Re: [v9.5] Custom Plan API

Started by Kohei KaiGaiover 11 years ago39 messages
#1Kohei KaiGai
kaigai@kaigai.gr.jp
1 attachment(s)

According to the discussion upthread, I revised the custom-plan patch
to focus on regular relation scan but no join support right now, and to
support DDL command to define custom-plan providers.

Planner integration with custom logic to scan a particular relation is
enough simple, unlike various join cases. It's almost similar to what
built-in logic are doing now - custom-plan provider adds a path node
with its cost estimation if it can offer alternative way to scan referenced
relation. (in case of no idea, it does not need to add any paths)

A new DDL syntax I'd like to propose is below:

CREATE CUSTOM PLAN <name> FOR <class> PROVIDER <function_name>;

<name> is as literal, put a unique identifier.
<class> is workload type to be offered by this custom-plan provider.
"scan" is the only option right now, that means base relation scan.
<function_name> is also as literal; it shall perform custom-plan provider.

A custom-plan provider function is assumed to take an argument of
"internal" type to deliver a set of planner information that is needed to
construct custom-plan pathnode.
In case of "scan" class, pointer towards an customScanArg object
shall be delivered on invocation of custom-plan provider.

typedef struct {
uint32 custom_class;
PlannerInfo *root;
RelOptInfo *baserel;
RangeTblEntry *rte;
} customScanArg;

In case when the custom-plan provider function being invoked thought
it can offer an alternative scan path on the relation of "baserel", things
to do is (1) construct a CustomPath (or its inherited data type) object
with a table of callback function pointers (2) put its own cost estimation,
and (3) call add_path() to register this path as an alternative one.

Once the custom-path was chosen by query planner, its CreateCustomPlan
callback is called to populate CustomPlan node based on the pathnode.
It also has a table of callback function pointers to handle various planner's
job in setrefs.c and so on.

Similarly, its CreateCustomPlanState callback is called to populate
CustomPlanState node based on the plannode. It also has a table of
callback function pointers to handle various executor's job during quey
execution.

Most of callback designs are not changed from the prior proposition in
v9.4 development cycle, however, here is a few changes.

* CustomPlan became to inherit Scan, and CustomPlanState became to
inherit ScanState. Because some useful routines to implement scan-
logic, like ExecScan, expects state-node has ScanState as its base
type, it's more kindness for extension side. (I'd like to avoid each
extension reinvent ExecScan by copy & paste!)
I'm not sure whether it should be a union of Join in the future, however,
it is a reasonable choice to have compatible layout with Scan/ScanState
to implement alternative "scan" logic.

* Exporting static functions - I still don't have a graceful answer here.
However, it is quite natural that extensions to follow up interface updates
on the future version up of PostgreSQL.
Probably, it shall become clear what class of functions shall be
exported and what class of functions shall be re-implemented within
extension side in the later discussion.
Right now, I exported minimum ones that are needed to implement
alternative scan method - contrib/ctidscan module.

Items to be discussed later:
* planner integration for relations join - probably, we may define new
custom-plan classes as alternative of hash-join, merge-join and
nest-loop. If core can know this custom-plan is alternative of hash-
join, we can utilize core code to check legality of join.
* generic key-value style options in custom-plan definition - Hanada
san proposed me off-list - like foreign data wrapper. It may enable
to configure multiple behavior on a binary.
* ownership and access control of custom-plan. right now, only
superuser can create/drop custom-plan provider definition, thus,
it has no explicit ownership and access control. It seems to me
a reasonable assumption, however, we may have a usecase that
needs custom-plan by unprivileged users.

Thanks,

2014-05-12 10:09 GMT+09:00 Kouhei Kaigai <kaigai@ak.jp.nec.com>:

On 8 May 2014 22:55, Tom Lane <tgl@sss.pgh.pa.us> wrote:

We're past the prototyping stage and into productionising what we
know works, AFAIK. If that point is not clear, then we need to
discuss that first.

OK, I'll bite: what here do we know works? Not a damn thing AFAICS;
it's all speculation that certain hooks might be useful, and
speculation that's not supported by a lot of evidence. If you think
this isn't prototyping, I wonder what you think *is* prototyping.

My research contacts advise me of this recent work
http://www.ntu.edu.sg/home/bshe/hashjoinonapu_vldb13.pdf
and also that they expect a prototype to be ready by October, which I have
been told will be open source.

So there are at least two groups looking at this as a serious option for
Postgres (not including the above paper's authors).

That isn't *now*, but it is at least a time scale that fits with acting
on this in the next release, if we can separate out the various ideas and
agree we wish to proceed.

I'll submerge again...

Through the discussion last week, our minimum consensus are:
1. Deregulated enhancement of FDW is not a way to go
2. Custom-path that can replace built-in scan makes sense as a first step
towards the future enhancement. Its planner integration is enough simple
to do.
3. Custom-path that can replace built-in join takes investigation how to
integrate existing planner structure, to avoid (3a) reinvention of
whole of join handling in extension side, and (3b) unnecessary extension
calls towards the case obviously unsupported.

So, I'd like to start the (2) portion towards the upcoming 1st commit-fest
on the v9.5 development cycle. Also, we will be able to have discussion
for the (3) portion concurrently, probably, towards 2nd commit-fest.

Unfortunately, I cannot participate PGcon/Ottawa this year. Please share
us the face-to-face discussion here.

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

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

Attachments:

pgsql-v9.5-custom-plan.v1.patchapplication/octet-stream; name=pgsql-v9.5-custom-plan.v1.patchDownload
 contrib/ctidscan/Makefile                     |  19 +
 contrib/ctidscan/ctidscan--1.0.sql            |  12 +
 contrib/ctidscan/ctidscan--unpackaged-1.0.sql |   0
 contrib/ctidscan/ctidscan.c                   | 951 ++++++++++++++++++++++++++
 contrib/ctidscan/ctidscan.control             |   5 +
 contrib/ctidscan/expected/ctidscan.out        | 294 ++++++++
 contrib/ctidscan/sql/ctidscan.sql             |  50 ++
 doc/src/sgml/catalogs.sgml                    |  59 ++
 doc/src/sgml/ref/allfiles.sgml                |   2 +
 doc/src/sgml/ref/create_custom_plan.sgml      | 135 ++++
 doc/src/sgml/ref/drop_custom_plan.sgml        | 108 +++
 src/backend/catalog/Makefile                  |   2 +-
 src/backend/catalog/dependency.c              |  11 +-
 src/backend/catalog/objectaddress.c           |  59 ++
 src/backend/commands/Makefile                 |   2 +-
 src/backend/commands/custom_plan.c            | 186 +++++
 src/backend/commands/dropcmds.c               |   5 +
 src/backend/commands/event_trigger.c          |   2 +
 src/backend/commands/explain.c                |  37 +
 src/backend/executor/Makefile                 |   2 +-
 src/backend/executor/execAmi.c                |  23 +
 src/backend/executor/execProcnode.c           |  19 +
 src/backend/executor/nodeCustom.c             | 135 ++++
 src/backend/nodes/copyfuncs.c                 |  34 +
 src/backend/nodes/equalfuncs.c                |  14 +
 src/backend/nodes/outfuncs.c                  |  33 +
 src/backend/optimizer/path/allpaths.c         |  30 +-
 src/backend/optimizer/plan/createplan.c       |  97 ++-
 src/backend/optimizer/plan/setrefs.c          |  11 +-
 src/backend/optimizer/plan/subselect.c        |  15 +
 src/backend/optimizer/util/pathnode.c         | 110 +++
 src/backend/parser/gram.y                     |  93 ++-
 src/backend/tcop/utility.c                    |  16 +
 src/backend/utils/adt/ruleutils.c             |  54 ++
 src/backend/utils/cache/syscache.c            |  23 +
 src/include/catalog/dependency.h              |   1 +
 src/include/catalog/indexing.h                |   6 +
 src/include/catalog/pg_custom_plan.h          |  49 ++
 src/include/catalog/pg_operator.h             |   3 +
 src/include/commands/defrem.h                 |   5 +
 src/include/executor/nodeCustom.h             |  30 +
 src/include/nodes/execnodes.h                 |  41 ++
 src/include/nodes/nodes.h                     |   5 +
 src/include/nodes/parsenodes.h                |  13 +
 src/include/nodes/plannodes.h                 |  40 ++
 src/include/nodes/relation.h                  |  28 +
 src/include/optimizer/pathnode.h              |  14 +
 src/include/optimizer/planmain.h              |   2 +
 src/include/parser/kwlist.h                   |   4 +
 src/include/utils/syscache.h                  |   2 +
 src/test/regress/expected/sanity_check.out    |   1 +
 51 files changed, 2856 insertions(+), 36 deletions(-)

diff --git a/contrib/ctidscan/Makefile b/contrib/ctidscan/Makefile
new file mode 100644
index 0000000..1e476a6
--- /dev/null
+++ b/contrib/ctidscan/Makefile
@@ -0,0 +1,19 @@
+# contrib/ctidscan/Makefile
+
+MODULES = ctidscan
+
+EXTENSION = ctidscan
+DATA = ctidscan--1.0.sql
+
+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--1.0.sql b/contrib/ctidscan/ctidscan--1.0.sql
new file mode 100644
index 0000000..3eec965
--- /dev/null
+++ b/contrib/ctidscan/ctidscan--1.0.sql
@@ -0,0 +1,12 @@
+--
+-- Create ctidscan handler function
+--
+CREATE FUNCTION ctidscanaddpath(internal)
+  RETURNS pg_catalog.void
+  AS 'MODULE_PATHNAME','CtidScanAddPath'
+  LANGUAGE C STRICT;
+
+--
+-- Create a custom-plan provider
+--
+CREATE CUSTOM PLAN ctidscan FOR scan PROVIDER ctidscanaddpath;
diff --git a/contrib/ctidscan/ctidscan--unpackaged-1.0.sql b/contrib/ctidscan/ctidscan--unpackaged-1.0.sql
new file mode 100644
index 0000000..e69de29
diff --git a/contrib/ctidscan/ctidscan.c b/contrib/ctidscan/ctidscan.c
new file mode 100644
index 0000000..460b38e
--- /dev/null
+++ b/contrib/ctidscan/ctidscan.c
@@ -0,0 +1,951 @@
+/*
+ * 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_custom_plan.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 {
+	CustomPlan		cplan;
+	List		   *ctid_quals;
+} CtidScanPlan;
+
+typedef struct {
+	CustomPlanState	cps;
+	List		   *ctid_quals;		/* list of ExprState for inequality ops */
+} CtidScanState;
+
+static CustomPathMethods	ctidscan_path_methods;
+static CustomPlanMethods	ctidscan_plan_methods;
+static CustomExecMethods	ctidscan_exec_methods;
+
+/* function declarations */
+void	_PG_init(void);
+Datum	CtidScanAddPath(PG_FUNCTION_ARGS);
+
+#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 undeterministic untill
+		 * executor run it actually.
+		 */
+		num_pages = Max((baserel->pages + 1) / 2, 1);
+	}
+	ntuples *= ((double) num_pages) / ((double) baserel->pages);
+
+	/*
+	 * The TID qual expressions will be computed once, any other baserestrict
+	 * quals once per 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;
+}
+
+/*
+ * CreateCtidScanPlan - A method of CustomPath; that populate a custom
+ * object being delivered from CustomPlan type, according to the supplied
+ * CustomPath object.
+ */
+static CustomPlan *
+CreateCtidScanPlan(PlannerInfo *root, CustomPath *best_path)
+{
+	CtidScanPath   *ctid_path = (CtidScanPath *) best_path;
+	CtidScanPlan   *ctid_plan;
+
+	ctid_plan = palloc0(sizeof(CtidScanPlan));
+	NodeSetTag(ctid_plan, T_CustomPlan);
+	ctid_plan->cplan.methods = &ctidscan_plan_methods;
+	ctid_plan->ctid_quals = ctid_path->ctid_quals;
+
+	return &ctid_plan->cplan;
+}
+
+/*
+ * InitCtidScanPlan
+ *
+ * main portion to initialize CustomPlan node.
+ */
+static Plan *
+InitCtidScanPlan(CustomPlan *custom_plan,
+				 PlannerInfo *root, CustomPath *best_path,
+				 List *tlist, List *clauses)
+{
+	CtidScanPlan   *ctid_scan = (CtidScanPlan *)custom_plan;
+
+	Assert(ctid_scan->cplan.scan.scanrelid > 0);
+
+	/* Set targetlist as is */
+	ctid_scan->cplan.scan.plan.targetlist = tlist;
+	/* Reduce RestrictInfo list to bare expressions */
+	ctid_scan->cplan.scan.plan.qual
+		= extract_actual_clauses(clauses, false);
+
+	/*
+	 * 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 &ctid_scan->cplan.scan.plan;
+}
+
+/*
+ * 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 CustomPlan; 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,
+				   CustomPlan *custom_plan,
+				   int rtoffset)
+{
+	CtidScanPlan   *ctidscan = (CtidScanPlan *) custom_plan;
+	Scan		   *scan = &ctidscan->cplan.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);
+}
+
+/*
+ * SupportCtidBackwardScan - A method of CustomPlan; that informs the core
+ * backend whether this custom-plan node support backward scan or not.
+ */
+static bool
+SupportCtidBackwardScan(CustomPlan *custom_plan)
+{
+	return true;
+}
+
+/*
+ * FinalizeCtidScanPlan - A method of CustomPlan; that handles callbacks
+ * by finalize_plan().
+ */
+static Bitmapset *
+FinalizeCtidScanPlan(PlannerInfo *root,
+					 CustomPlan *custom_plan,
+					 Bitmapset *paramids,
+					 Bitmapset *valid_params,
+					 Bitmapset *scan_params,
+					 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);
+
+	return bms_add_members(paramids, scan_params);
+}
+
+/*
+ * CreateCtidScanState - A method of CustomPlan; that populate a custom
+ * object being delivered from CustomPlanState type, according to the
+ * supplied CustomPath object.
+ */
+static CustomPlanState *
+CreateCtidScanState(CustomPlan *custom_plan)
+{
+	CtidScanState  *ctss = palloc0(sizeof(CtidScanState));
+
+	NodeSetTag(ctss, T_CustomPlanState);
+	ctss->cps.methods = &ctidscan_exec_methods;
+
+	return &ctss->cps;
+}
+
+/*
+ * TextOutCtidScanPlan - A method of CustomPlan; that generates text
+ * representation of the given object.
+ */
+static void
+TextOutCtidScanPlan(StringInfo str, const CustomPlan *node)
+{
+	CtidScanPlan   *ctid_plan = (CtidScanPlan *) node;
+
+	appendStringInfo(str, " :ctid_quals %s",
+					 nodeToString(ctid_plan->ctid_quals));
+}
+
+/*
+ * CopyCtidScanPlan - A method of CustomPlan; that create a copied object.
+ */
+static CustomPlan *
+CopyCtidScanPlan(const CustomPlan *from)
+{
+	CtidScanPlan   *oldnode = (CtidScanPlan *) from;
+	CtidScanPlan   *newnode = palloc0(sizeof(CtidScanPlan));
+
+	NodeSetTag(newnode, T_CustomPlan);
+	newnode->ctid_quals = copyObject(oldnode->ctid_quals);
+
+	return &newnode->cplan;
+}
+
+/*
+ * BeginCtidScan - A method of CustomPlanState; that initializes
+ * the supplied CtidScanState object, at begining of the executor.
+ */
+static void
+BeginCtidScan(CustomPlanState *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->cps.ss.ss_currentScanDesc = NULL;
+}
+
+/*
+ * ReScanCtidScan - A method of CustomPlanState; that rewind the current
+ * seek position.
+ */
+static void
+ReScanCtidScan(CustomPlanState *node)
+{
+	CtidScanState  *ctss = (CtidScanState *)node;
+	HeapScanDesc	scan = ctss->cps.ss.ss_currentScanDesc;
+	EState		   *estate = node->ss.ps.state;
+	ScanDirection	direction = estate->es_direction;
+	Relation		relation = ctss->cps.ss.ss_currentRelation;
+	ExprContext	   *econtext = ctss->cps.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->cps.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->cps.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->cps.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(CustomPlanState *node)
+{
+	CtidScanState  *ctss = (CtidScanState *) node;
+	HeapScanDesc	scan;
+	TupleTableSlot *slot;
+	EState		   *estate = node->ss.ps.state;
+	ScanDirection	direction = estate->es_direction;
+	HeapTuple		tuple;
+
+	if (!ctss->cps.ss.ss_currentScanDesc)
+		ReScanCtidScan(node);
+	scan = ctss->cps.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->cps.ss.ss_ScanTupleSlot;
+	ExecStoreTuple(tuple, slot, scan->rs_cbuf, false);
+
+	return slot;
+}
+
+static bool
+CTidRecheckCustomScan(CustomPlanState *node, TupleTableSlot *slot)
+{
+	return true;
+}
+
+/*
+ * ExecCtidScan - A method of CustomPlanState; that fetches a tuple
+ * from the relation, if exist anymore.
+ */
+static TupleTableSlot *
+ExecCtidScan(CustomPlanState *node)
+{
+	return ExecScan(&node->ss,
+					(ExecScanAccessMtd) CTidAccessCustomScan,
+					(ExecScanRecheckMtd) CTidRecheckCustomScan);
+}
+
+/*
+ * CTidEndCustomScan - A method of CustomPlanState; that closes heap and
+ * scan descriptor, and release other related resources.
+ */
+static void
+EndCtidScan(CustomPlanState *node)
+{
+	CtidScanState  *ctss = (CtidScanState *)node;
+
+	if (ctss->cps.ss.ss_currentScanDesc)
+		heap_endscan(ctss->cps.ss.ss_currentScanDesc);
+}
+
+/*
+ * ExplanCtidScanTargetRel - A method of CustomPlanState; that output
+ * relation's name to be scanned.
+ */
+static void
+ExplanCtidScanTargetRel(CustomPlanState *node, ExplainState *es)
+{
+	CtidScanState  *ctss = (CtidScanState *) node;
+	CtidScanPlan   *ctid_plan = (CtidScanPlan *) ctss->cps.ss.ps.plan;
+	Index			rti = ctid_plan->cplan.scan.scanrelid;
+	RangeTblEntry   *rte;
+	char		   *objectname = NULL;
+	char		   *namespace = NULL;
+	char		   *refname;
+
+	/* logic copied from ExplainTargetRel */
+	rte = rt_fetch(rti, es->rtable);
+	refname = (char *) list_nth(es->rtable_names, rti - 1);
+	if (refname == NULL)
+		refname = rte->eref->aliasname;
+
+	Assert(rte->rtekind == RTE_RELATION);
+	objectname = get_rel_name(rte->relid);
+	if (es->verbose)
+		namespace = get_namespace_name(get_rel_namespace(rte->relid));
+
+	if (es->format == EXPLAIN_FORMAT_TEXT)
+	{
+		appendStringInfoString(es->str, " on");
+		if (namespace != NULL)
+			appendStringInfo(es->str, " %s.%s", quote_identifier(namespace),
+							 quote_identifier(objectname));
+		else if (objectname != NULL)
+			appendStringInfo(es->str, " %s", quote_identifier(objectname));
+		if (objectname == NULL || strcmp(refname, objectname) != 0)
+			appendStringInfo(es->str, " %s", quote_identifier(refname));
+	}
+	else
+	{
+		if (objectname != NULL)
+			ExplainPropertyText("Relation Name", objectname, es);
+		if (namespace != NULL)
+			ExplainPropertyText("Schema", namespace, es);
+		ExplainPropertyText("Alias", refname, es);
+	}
+}
+
+/*
+ * ExplainCtidScan - A method of CustomPlanState; that shows extra info
+ * on EXPLAIN command.
+ */
+static void
+ExplainCtidScan(CustomPlanState *node, List *ancestors, ExplainState *es)
+{
+	CtidScanState  *ctss = (CtidScanState *) node;
+	CtidScanPlan   *ctid_plan = (CtidScanPlan *) ctss->cps.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);
+	}
+}
+
+/*
+ * ExplainCtidPreScanNode - A method of CustomPlanState; that informs
+ * the core backend relation's rtindex to be referenced, prior to the
+ * main EXPLAIN processing.
+ */
+static void
+ExplainCtidPreScanNode(CustomPlanState *node, Bitmapset **rels_used)
+{
+	CtidScanState  *ctss = (CtidScanState *) node;
+	Index			scanrelid = ((Scan *)ctss->cps.ss.ps.plan)->scanrelid;
+
+	*rels_used = bms_add_member(*rels_used, scanrelid);
+}
+
+/*
+ * Entrypoint of this extension
+ */
+Datum
+CtidScanAddPath(PG_FUNCTION_ARGS)
+{
+	customScanArg  *cscan_arg = (customScanArg *)PG_GETARG_POINTER(0);
+	PlannerInfo	   *root;
+	RangeTblEntry  *rte;
+	RelOptInfo	   *baserel;
+	char			relkind;
+	ListCell	   *lc;
+	List		   *ctid_quals = NIL;
+
+	if (cscan_arg->custom_class != CUSTOMPLAN_CLASS_SCAN)
+		PG_RETURN_VOID();
+
+	root = cscan_arg->root;
+	rte = cscan_arg->rte;
+	baserel = cscan_arg->baserel;
+
+	/* all we can support is regular relations */
+	if (rte->rtekind != RTE_RELATION)
+		PG_RETURN_VOID();
+
+	relkind = get_rel_relkind(rte->relid);
+	if (relkind != RELKIND_RELATION &&
+		relkind != RELKIND_MATVIEW &&
+		relkind != RELKIND_TOASTVALUE)
+		PG_RETURN_VOID();
+
+	/* 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_CustomPlan;
+        ctid_path->cpath.path.parent = baserel;
+        ctid_path->cpath.path.param_info
+			= get_baserel_parampathinfo(root, baserel, required_outer);
+		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);
+    }
+	PG_RETURN_VOID();
+}
+PG_FUNCTION_INFO_V1(CtidScanAddPath);
+
+/*
+ * Entrypoint of this extension
+ */
+void
+_PG_init(void)
+{
+	/* setup ctidscan_path_methods */
+	ctidscan_path_methods.CustomName = "ctidscan";
+	ctidscan_path_methods.CreateCustomPlan = CreateCtidScanPlan;
+	ctidscan_path_methods.TextOutCustomPath = TextOutCtidScanPath;
+
+	/* setup ctidscan_plan_methods */
+	ctidscan_plan_methods.CustomName = "ctidscan";
+	ctidscan_plan_methods.InitCustomPlan = InitCtidScanPlan;
+	ctidscan_plan_methods.SetCustomPlanRef = SetCtidScanPlanRef;
+	ctidscan_plan_methods.SupportBackwardScan = SupportCtidBackwardScan;
+	ctidscan_plan_methods.FinalizeCustomPlan = FinalizeCtidScanPlan;
+	ctidscan_plan_methods.CreateCustomPlanState = CreateCtidScanState;
+	ctidscan_plan_methods.TextOutCustomPlan = TextOutCtidScanPlan;
+	ctidscan_plan_methods.CopyCustomPlan = CopyCtidScanPlan;
+
+	/* setup ctidscan_planstate_methods */
+	ctidscan_exec_methods.CustomName = "ctidscan";
+	ctidscan_exec_methods.BeginCustomPlan = BeginCtidScan;
+	ctidscan_exec_methods.ExecCustomPlan = ExecCtidScan;
+	ctidscan_exec_methods.EndCustomPlan = EndCtidScan;
+	ctidscan_exec_methods.ReScanCustomPlan = ReScanCtidScan;
+	ctidscan_exec_methods.MarkPosCustomPlan = NULL;
+	ctidscan_exec_methods.RestrPosCustomPlan = NULL;
+	ctidscan_exec_methods.ExplainCustomPlanTargetRel = ExplanCtidScanTargetRel;
+	ctidscan_exec_methods.ExplainCustomPlan = ExplainCtidScan;
+	ctidscan_exec_methods.ExplainCustomPreScanNode = ExplainCtidPreScanNode;
+	ctidscan_exec_methods.GetSpecialCustomVar = NULL;
+}
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..ae9c628
--- /dev/null
+++ b/contrib/ctidscan/expected/ctidscan.out
@@ -0,0 +1,294 @@
+--
+-- Regression Tests for Custom Plan APIs
+--
+CREATE EXTENSION ctidscan;
+-- 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                              
+----------------------------------------------------------------------
+ Custom (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 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 (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 (ctidscan) on t1
+               Filter: (ctid < '(2,10)'::tid)
+               ctid quals: (ctid < '(2,10)'::tid)
+   ->  Sort
+         Sort Key: t2.ctid
+         ->  Custom (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)
+
+-- 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..9759065
--- /dev/null
+++ b/contrib/ctidscan/sql/ctidscan.sql
@@ -0,0 +1,50 @@
+--
+-- Regression Tests for Custom Plan APIs
+--
+
+CREATE EXTENSION ctidscan;
+
+-- 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;
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE b like '%789%';
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid = '(2,10)'::tid;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+EXPLAIN (costs off) SELECT * FROM t1 JOIN t2 ON t1.ctid = t2.ctid WHERE t1.ctid < '(2,10)'::tid AND t2.ctid > '(1,75)'::tid;
+
+SELECT ctid,* FROM t1 WHERE ctid < '(1,20)'::tid;
+SELECT ctid,* FROM t1 WHERE ctid > '(4,0)'::tid;
+SELECT ctid,* FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+SELECT t1.ctid,* FROM t1 JOIN t2 ON t1.ctid = t2.ctid WHERE t1.ctid < '(2,10)'::tid AND t2.ctid > '(1,75)'::tid;
+
+
+-- Test cleanup
+DROP SCHEMA regtest_custom_scan CASCADE;
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index b4a06e4..4742231 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -109,6 +109,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-custom-plan"><structname>pg_custom_plan</structname></link></entry>
+      <entry>custom plan providers</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-conversion"><structname>pg_conversion</structname></link></entry>
       <entry>encoding conversion information</entry>
      </row>
@@ -2508,6 +2513,60 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-custom-plan">
+  <title><structname>pg_custom_plan</structname></title>
+
+  <indexterm zone="catalog-pg-custom-plan">
+   <primary>pg_custom_plan</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_custom_plan</structname> describes
+   custom-plan providers. See <xref linkend="sql-createcustomplan">
+   for more information.
+  </para>
+
+  <table>
+   <title><structname>pg_custom_plan</> Columns</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>References</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><structfield>oid</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry></entry>
+      <entry>Row identifier (hidden attribute; must be explicitly selected)</entry>
+     </row>
+     <row>
+      <entry><structfield>custname</structfield></entry>
+      <entry><type>name</type></entry>
+      <entry></entry>
+      <entry>custom-plan name</entry>
+     </row>
+     <row>
+      <entry><structfield>custclass</structfield></entry>
+      <entry><type>char</type></entry>
+      <entry></entry>
+      <entry>class of plan node on which custom-plna performs</entry>
+     </row>
+     <row>
+      <entry><structfield>custprovider</structfield></entry>
+      <entry><type>regproc</type></entry>
+      <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
+      <entry>custom-plan provder function</entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
  <sect1 id="catalog-pg-database">
   <title><structname>pg_database</structname></title>
 
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index 1b0962c..f1b87f0 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -55,6 +55,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY createCast         SYSTEM "create_cast.sgml">
 <!ENTITY createCollation    SYSTEM "create_collation.sgml">
 <!ENTITY createConversion   SYSTEM "create_conversion.sgml">
+<!ENTITY createCustomPlan   SYSTEM "create_custom_plan.sgml">
 <!ENTITY createDatabase     SYSTEM "create_database.sgml">
 <!ENTITY createDomain       SYSTEM "create_domain.sgml">
 <!ENTITY createEventTrigger SYSTEM "create_event_trigger.sgml">
@@ -95,6 +96,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY dropCast           SYSTEM "drop_cast.sgml">
 <!ENTITY dropCollation      SYSTEM "drop_collation.sgml">
 <!ENTITY dropConversion     SYSTEM "drop_conversion.sgml">
+<!ENTITY dropCustomPlan     SYSTEM "drop_custom_plan.sgml">
 <!ENTITY dropDatabase       SYSTEM "drop_database.sgml">
 <!ENTITY dropDomain         SYSTEM "drop_domain.sgml">
 <!ENTITY dropEventTrigger   SYSTEM "drop_event_trigger.sgml">
diff --git a/doc/src/sgml/ref/create_custom_plan.sgml b/doc/src/sgml/ref/create_custom_plan.sgml
new file mode 100644
index 0000000..c7294c8
--- /dev/null
+++ b/doc/src/sgml/ref/create_custom_plan.sgml
@@ -0,0 +1,135 @@
+<!--
+doc/src/sgml/ref/create_custom_plan.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-CUSTOMPLAN">
+ <indexterm zone="sql-customplan">
+  <primary>CREATE CUSTOM PLAN</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>CREATE CUSTOM PLAN</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>CREATE CUSTOM PLAN</refname>
+  <refpurpose>define a new custom plan provider</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+CREATE CUSTOM PLAN <replaceable class="parameter">provider_name</replaceable> FOR <replaceable class="parameter">custom_class</replaceable>
+    PROVIDER <replaceable class="parameter">provider_function</replaceable>
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>CREATE CUSTOM PLAN</command> defines a new custom-plan provider.
+   The user who defines the custom-plan provider has to be a superuser.
+  </para>
+
+  <para>
+   A custom-plan provider can offer the query planenr alternative options
+   to scan relation, or potentially join relations and so on, in addition
+   to the built-in logics. It is usually extension modules that implements
+   callbacks according to the custom-plan interface.
+  </para>
+  <para>
+   This statement defines a couple of an entrypoint of custom-plan provider
+   and its supporting workload type. Right now, <literal>scan</literal> is
+   the only class being supported; that enables to call extension's
+   callback during query execution instead of built-in routines like
+   <literal>SeqScan</literal> or <literal>IndexScan</literal> if its
+   cost estimation is enough reasonable.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">provider_name</replaceable></term>
+    <listitem>
+     <para>
+      The name of the custom-plan provider to be created.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">custom_class</replaceable></term>
+    <listitem>
+     <para>
+      Workload type on which custom-plan provider can perform.
+      Only <literal>scan</literal> is supported option right now.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">provider_function</replaceable></term>
+    <listitem>
+     <para>
+      A function to be called when query planner is finding the best path
+      to scan a relation.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+   The function that performs as a custom-plan provider shall be declared
+   to return <literal>void</> and take one argument with <literal>internal</>
+   data type.
+   The core <productname>PostgreSQL</> calls custom-plan provider function
+   with a set of information about planner's state and relation(s) to be
+   scanned, then this function shall check whether it can offer alternative
+   scan paths or not.
+   If available, it constructs a path object derived from
+   <literal>CustomPath</> structure, that contains a set of callbacks
+   including the ones to populate <literal>CustomPlan</> or
+   <litaral>CustomPlanState</> object later.
+   If extension needs to save its private information in these object,
+   define a new structure that extends above data types.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+  <para>
+   Create a custom-plan provider <literal>ctidscan</> that uses the funcion
+   <literal>ctidscanaddpath</>.   
+<programlisting>
+CREATE CUSTOM PLAN ctidscan FOR scan PROVIDER ctidscanaddpath;
+</programlisting>
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+  <para>
+   There is no <command>CREATE CUSTOM PLAN</command> command
+   in the SQL standard.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-dropcustomplan"></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/drop_custom_plan.sgml b/doc/src/sgml/ref/drop_custom_plan.sgml
new file mode 100644
index 0000000..b72146b
--- /dev/null
+++ b/doc/src/sgml/ref/drop_custom_plan.sgml
@@ -0,0 +1,108 @@
+<!--
+doc/src/sgml/ref/drop_custom_plan.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-DROPCUSTOMPLAN">
+ <indexterm zone="sql-dropcustomplan">
+  <primary>DROP CUSTOM PLAN</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>DROP CUSTOM PLAN</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>DROP CUSTOM PLAN</refname>
+  <refpurpose>remove a custom-plan provider</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+DROP CUSTOM PLAN [ IF EXISTS ] <replaceable class="parameter">name</replaceable> [ CASCADE | RESTRICT ]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>DROP CUSTOM PLAN</command> removes an existing custom-plan
+   provider. To execute this command, the current user must be superuser.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>IF EXISTS</literal></term>
+    <listitem>
+     <para>
+      Do not throw an error if the custom-plan provider does not exist.
+      A notice is issued in this case.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name of an existing custom-plan provider.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>CASCADE</literal></term>
+    <listitem>
+     <para>
+      Automatically drop objects that depend on the custom-plan provider.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>RESTRICT</literal></term>
+    <listitem>
+     <para>
+      Refuse to drop the custom-plan provider if any objects depend on it.
+      This is the default.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   Drop a custom-plan provider <literal>foo</> if it exists:
+<programlisting>
+DROP CUSTOM PLAN IF EXISTS foo;
+</programlisting></para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   There is no <command>DROP CUSTOM PLAN</command> command
+   in the SQL standard.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-createcustomplan"></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index a974bd5..83b02e9 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -39,7 +39,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
 	pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
 	pg_ts_parser.h pg_ts_template.h pg_extension.h \
 	pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
-	pg_foreign_table.h \
+	pg_foreign_table.h pg_custom_plan.h \
 	pg_default_acl.h pg_seclabel.h pg_shseclabel.h pg_collation.h pg_range.h \
 	toasting.h indexing.h \
     )
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index d41ba49..ec01170 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -30,6 +30,7 @@
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_conversion.h"
 #include "catalog/pg_conversion_fn.h"
+#include "catalog/pg_custom_plan.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_default_acl.h"
 #include "catalog/pg_depend.h"
@@ -154,7 +155,8 @@ static const Oid object_classes[MAX_OCLASS] = {
 	UserMappingRelationId,		/* OCLASS_USER_MAPPING */
 	DefaultAclRelationId,		/* OCLASS_DEFACL */
 	ExtensionRelationId,		/* OCLASS_EXTENSION */
-	EventTriggerRelationId		/* OCLASS_EVENT_TRIGGER */
+	EventTriggerRelationId,		/* OCLASS_EVENT_TRIGGER */
+	CustomPlanRelationId,		/* OCLASS_CUSTOM_PLAN */
 };
 
 
@@ -1249,6 +1251,10 @@ doDeletion(const ObjectAddress *object, int flags)
 			RemoveEventTriggerById(object->objectId);
 			break;
 
+		case OCLASS_CUSTOM_PLAN:
+			RemoveCustomPlanById(object->objectId);
+			break;
+
 		default:
 			elog(ERROR, "unrecognized object class: %u",
 				 object->classId);
@@ -2316,6 +2322,9 @@ getObjectClass(const ObjectAddress *object)
 
 		case EventTriggerRelationId:
 			return OCLASS_EVENT_TRIGGER;
+
+		case CustomPlanRelationId:
+			return OCLASS_CUSTOM_PLAN;
 	}
 
 	/* shouldn't get here */
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index c7c8f4b..2a5b5cf 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -30,6 +30,7 @@
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_conversion.h"
+#include "catalog/pg_custom_plan.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_extension.h"
 #include "catalog/pg_foreign_data_wrapper.h"
@@ -152,6 +153,18 @@ static const ObjectPropertyType ObjectProperty[] =
 		true
 	},
 	{
+		CustomPlanRelationId,
+		CustomPlanOidIndexId,
+		CUSTOMPLANOID,
+		CUSTOMPLANNAME,
+		Anum_pg_custom_plan_custname,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		-1,
+		false,
+	},
+	{
 		DatabaseRelationId,
 		DatabaseOidIndexId,
 		DATABASEOID,
@@ -529,6 +542,7 @@ get_object_address(ObjectType objtype, List *objname, List *objargs,
 			case OBJECT_FDW:
 			case OBJECT_FOREIGN_SERVER:
 			case OBJECT_EVENT_TRIGGER:
+			case OBJECT_CUSTOM_PLAN:
 				address = get_object_address_unqualified(objtype,
 														 objname, missing_ok);
 				break;
@@ -755,6 +769,9 @@ get_object_address_unqualified(ObjectType objtype,
 			case OBJECT_EVENT_TRIGGER:
 				msg = gettext_noop("event trigger name cannot be qualified");
 				break;
+			case OBJECT_CUSTOM_PLAN:
+				msg = gettext_noop("custom plan name cannot be qualified");
+				break;
 			default:
 				elog(ERROR, "unrecognized objtype: %d", (int) objtype);
 				msg = NULL;		/* placate compiler */
@@ -815,6 +832,11 @@ get_object_address_unqualified(ObjectType objtype,
 			address.objectId = get_event_trigger_oid(name, missing_ok);
 			address.objectSubId = 0;
 			break;
+		case OBJECT_CUSTOM_PLAN:
+			address.classId = CustomPlanRelationId;
+			address.objectId = get_custom_plan_oid(name, missing_ok);
+			address.objectSubId = 0;
+			break;
 		default:
 			elog(ERROR, "unrecognized objtype: %d", (int) objtype);
 			/* placate compiler, which doesn't know elog won't return */
@@ -1295,6 +1317,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
 			break;
 		case OBJECT_TSPARSER:
 		case OBJECT_TSTEMPLATE:
+		case OBJECT_CUSTOM_PLAN:
 			/* We treat these object types as being owned by superusers */
 			if (!superuser_arg(roleid))
 				ereport(ERROR,
@@ -2166,6 +2189,21 @@ getObjectDescription(const ObjectAddress *object)
 				break;
 			}
 
+		case OCLASS_CUSTOM_PLAN:
+			{
+				HeapTuple	tup;
+
+				tup = SearchSysCache1(CUSTOMPLANOID,
+									  ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "cache lookup failed for custom-plan %u",
+						 object->objectId);
+				appendStringInfo(&buffer, _("custom plan %s"),
+					NameStr(((Form_pg_custom_plan) GETSTRUCT(tup))->custname));
+				ReleaseSysCache(tup);
+				break;
+			}
+
 		default:
 			appendStringInfo(&buffer, "unrecognized object %u %u %d",
 							 object->classId,
@@ -2577,6 +2615,10 @@ getObjectTypeDescription(const ObjectAddress *object)
 			appendStringInfoString(&buffer, "event trigger");
 			break;
 
+		case OCLASS_CUSTOM_PLAN:
+			appendStringInfoString(&buffer, "custom plan");
+			break;
+
 		default:
 			appendStringInfo(&buffer, "unrecognized %u", object->classId);
 			break;
@@ -3330,6 +3372,23 @@ getObjectIdentity(const ObjectAddress *object)
 				break;
 			}
 
+		case OCLASS_CUSTOM_PLAN:
+			{
+				HeapTuple	tup;
+				Form_pg_custom_plan custForm;
+
+				tup = SearchSysCache1(CUSTOMPLANOID,
+									  ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "cache lookup failed for custom-plan %u",
+						 object->objectId);
+				custForm = (Form_pg_custom_plan) GETSTRUCT(tup);
+				appendStringInfoString(&buffer,
+						quote_identifier(NameStr(custForm->custname)));
+				ReleaseSysCache(tup);
+				break;
+			}
+
 		default:
 			appendStringInfo(&buffer, "unrecognized object %u %u %d",
 							 object->classId,
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index 22f116b..1e8e6f4 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -14,7 +14,7 @@ include $(top_builddir)/src/Makefile.global
 
 OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o  \
 	collationcmds.o constraint.o conversioncmds.o copy.o createas.o \
-	dbcommands.o define.o discard.o dropcmds.o \
+	custom_plan.o dbcommands.o define.o discard.o dropcmds.o \
 	event_trigger.o explain.o extension.o foreigncmds.o functioncmds.o \
 	indexcmds.o lockcmds.o matview.o operatorcmds.o opclasscmds.o \
 	portalcmds.o prepare.o proclang.o \
diff --git a/src/backend/commands/custom_plan.c b/src/backend/commands/custom_plan.c
new file mode 100644
index 0000000..27cd75d
--- /dev/null
+++ b/src/backend/commands/custom_plan.c
@@ -0,0 +1,186 @@
+/*-------------------------------------------------------------------------
+ *
+ * custom_plan.c
+ *		custom plan nodes creation/manipulation commands
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ *    src/backend/commands/custom_plan.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_custom_plan.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+#include "commands/defrem.h"
+#include "fmgr.h"
+#include "miscadmin.h"
+#include "parser/parse_func.h"
+#include "utils/builtins.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+/*
+ * utility function to lookup a custom-plan provider by name
+ */
+Oid
+get_custom_plan_oid(const char *custom_name, bool missing_ok)
+{
+	Oid		cust_oid;
+
+	cust_oid = GetSysCacheOid1(CUSTOMPLANNAME, CStringGetDatum(custom_name));
+	if (!OidIsValid(cust_oid) && !missing_ok)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("custom-plan provider \"%s\" does not exist",
+						custom_name)));
+	return cust_oid;
+}
+
+/*
+ * Drop a custom-plan provider
+ */
+void
+RemoveCustomPlanById(Oid cust_oid)
+{
+	Relation	rel;
+	HeapTuple	tuple;
+
+	rel = heap_open(CustomPlanRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCache1(CUSTOMPLANOID, ObjectIdGetDatum(cust_oid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for custom-plan provider %u",
+			 cust_oid);
+
+	simple_heap_delete(rel, &tuple->t_self);
+
+	ReleaseSysCache(tuple);
+
+	heap_close(rel, RowExclusiveLock);
+}
+
+/*
+ * Create a custom-plan provider
+ */
+Oid
+DefineCustomPlan(CreateCustomPlanStmt *stmt)
+{
+	Relation	rel;
+	Oid			cust_oid;
+	Oid			cust_provider = InvalidOid;
+	Datum		values[Natts_pg_custom_plan];
+	bool		isnull[Natts_pg_custom_plan];
+	HeapTuple	tuple;
+	ListCell   *cell;
+	ObjectAddress myself;
+	ObjectAddress referenced;
+
+	rel = heap_open(CustomPlanRelationId, RowExclusiveLock);
+
+	/* must be super user */
+	if (!superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+			 errmsg("permission denied to create custom-plan provider \"%s\"",
+					stmt->custom_name),
+			 errhint("Must be superuser to create a custom-plan node.")));
+
+	/* check namespace conflicts */
+	cust_oid = get_custom_plan_oid(stmt->custom_name, true);
+	if (OidIsValid(cust_oid))
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("custom-plan provider \"%s\" already exists",
+						stmt->custom_name)));
+
+	/* check custom-plan class */
+	if (stmt->custom_class != CUSTOMPLAN_CLASS_SCAN)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("unexpected custom plan class specified: %d",
+						(int)stmt->custom_class)));
+
+	/* parse custom-plan options */
+	foreach (cell, stmt->custom_options)
+	{
+		DefElem	   *defel = lfirst(cell);
+
+		Assert(IsA(defel, DefElem));
+
+		if (strcmp(defel->defname, "provider") == 0)
+		{
+			Oid		argtypes[1];
+
+			if (OidIsValid(cust_provider))
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+
+			argtypes[0] = INTERNALOID;
+			cust_provider = LookupFuncName((List *)defel->arg,
+										  1, argtypes, false);
+			if (get_func_rettype(cust_provider) != VOIDOID)
+				ereport(ERROR,
+						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+						 errmsg("function %s must return type \"void\"",
+								NameListToString((List *) defel->arg))));
+		}
+		else
+			elog(ERROR, "unexpected custom-plan option: %s",
+				 defel->defname);
+	}
+
+	if (!OidIsValid(cust_provider))
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("PROVIDER must be provided")));
+
+	/*
+	 * Insert tuple into pg_custom_plan system catalog
+	 */
+	memset(values, 0, sizeof(values));
+	memset(isnull, 0, sizeof(isnull));
+	values[Anum_pg_custom_plan_custname - 1]
+		= DirectFunctionCall1(namein, CStringGetDatum(stmt->custom_name));
+	values[Anum_pg_custom_plan_custclass - 1]
+		= stmt->custom_class;
+	values[Anum_pg_custom_plan_custprovider - 1]
+		= ObjectIdGetDatum(cust_provider);
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), values, isnull);
+
+	cust_oid = simple_heap_insert(rel, tuple);
+	CatalogUpdateIndexes(rel, tuple);
+
+	heap_freetuple(tuple);
+
+	/* record dependencies */
+	myself.classId = CustomPlanRelationId;
+	myself.objectId = cust_oid;
+	myself.objectSubId = 0;
+
+	referenced.classId = ProcedureRelationId;
+	referenced.objectId = cust_provider;
+	referenced.objectSubId = 0;
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+	/* Post creation hook for new custom-plan provider */
+	InvokeObjectPostCreateHook(CustomPlanRelationId, cust_oid, 0);
+
+	heap_close(rel, RowExclusiveLock);
+
+	return cust_oid;
+}
diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c
index e64ad80..104ff17 100644
--- a/src/backend/commands/dropcmds.c
+++ b/src/backend/commands/dropcmds.c
@@ -408,6 +408,11 @@ does_not_exist_skipping(ObjectType objtype, List *objname, List *objargs)
 				args = strVal(linitial(objargs));
 			}
 			break;
+		case OBJECT_CUSTOM_PLAN:
+			msg = gettext_noop("custom-plan \"%s\" does not exist, skipping");
+			name = NameListToString(objname);
+			break;
+
 		default:
 			elog(ERROR, "unexpected object type (%d)", (int) objtype);
 			break;
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 6d4e091..a3468f3 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -923,6 +923,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
 		case OBJECT_CONSTRAINT:
 		case OBJECT_COLLATION:
 		case OBJECT_CONVERSION:
+		case OBJECT_CUSTOM_PLAN:
 		case OBJECT_DOMAIN:
 		case OBJECT_EXTENSION:
 		case OBJECT_FDW:
@@ -975,6 +976,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
 		case OCLASS_COLLATION:
 		case OCLASS_CONSTRAINT:
 		case OCLASS_CONVERSION:
+		case OCLASS_CUSTOM_PLAN:
 		case OCLASS_DEFAULT:
 		case OCLASS_LANGUAGE:
 		case OCLASS_LARGEOBJECT:
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 0d9663c..450a123 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -722,6 +722,14 @@ ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
 			*rels_used = bms_add_member(*rels_used,
 										((Scan *) plan)->scanrelid);
 			break;
+		case T_CustomPlan:
+		case T_CustomPlanMarkPos:
+			{
+				CustomPlanState *cps = (CustomPlanState *)planstate;
+
+				cps->methods->ExplainCustomPreScanNode(cps, rels_used);
+			}
+			break;
 		case T_ModifyTable:
 			/* cf ExplainModifyTarget */
 			*rels_used = bms_add_member(*rels_used,
@@ -848,6 +856,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 +945,14 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_ForeignScan:
 			pname = sname = "Foreign Scan";
 			break;
+		case T_CustomPlan:
+			sname = "Custom";
+			custom_name = ((CustomPlan *) plan)->methods->CustomName;
+			if (custom_name)
+				pname = psprintf("Custom (%s)", custom_name);
+			else
+				pname = sname;
+			break;
 		case T_Material:
 			pname = sname = "Materialize";
 			break;
@@ -1037,6 +1054,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", custom_name, es);
 	}
 
 	switch (nodeTag(plan))
@@ -1084,6 +1103,13 @@ ExplainNode(PlanState *planstate, List *ancestors,
 					ExplainPropertyText("Index Name", indexname, es);
 			}
 			break;
+		case T_CustomPlan:
+			{
+				CustomPlanState *cps = (CustomPlanState *)planstate;
+
+				cps->methods->ExplainCustomPlanTargetRel(cps, es);
+			}
+			break;
 		case T_ModifyTable:
 			ExplainModifyTarget((ModifyTable *) plan, es);
 			break;
@@ -1353,6 +1379,17 @@ ExplainNode(PlanState *planstate, List *ancestors,
 										   planstate, es);
 			show_foreignscan_info((ForeignScanState *) planstate, es);
 			break;
+		case T_CustomPlan:
+			{
+				CustomPlanState *cps = (CustomPlanState *) planstate;
+
+				show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
+				if (plan->qual)
+					show_instrumentation_count("Rows Removed by Filter", 1,
+											   planstate, es);
+				cps->methods->ExplainCustomPlan(cps, 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..5fc9cbb 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -21,6 +21,7 @@
 #include "executor/nodeBitmapIndexscan.h"
 #include "executor/nodeBitmapOr.h"
 #include "executor/nodeCtescan.h"
+#include "executor/nodeCustom.h"
 #include "executor/nodeForeignscan.h"
 #include "executor/nodeFunctionscan.h"
 #include "executor/nodeGroup.h"
@@ -197,6 +198,10 @@ ExecReScan(PlanState *node)
 			ExecReScanForeignScan((ForeignScanState *) node);
 			break;
 
+		case T_CustomPlanState:
+			ExecReScanCustomPlan((CustomPlanState *) node);
+			break;
+
 		case T_NestLoopState:
 			ExecReScanNestLoop((NestLoopState *) node);
 			break;
@@ -291,6 +296,10 @@ ExecMarkPos(PlanState *node)
 			ExecValuesMarkPos((ValuesScanState *) node);
 			break;
 
+		case T_CustomPlanState:
+			ExecCustomMarkPos((CustomPlanState *) node);
+			break;
+
 		case T_MaterialState:
 			ExecMaterialMarkPos((MaterialState *) node);
 			break;
@@ -348,6 +357,10 @@ ExecRestrPos(PlanState *node)
 			ExecValuesRestrPos((ValuesScanState *) node);
 			break;
 
+		case T_CustomPlanState:
+			ExecCustomRestrPos((CustomPlanState *) node);
+			break;
+
 		case T_MaterialState:
 			ExecMaterialRestrPos((MaterialState *) node);
 			break;
@@ -390,6 +403,7 @@ ExecSupportsMarkRestore(NodeTag plantype)
 		case T_ValuesScan:
 		case T_Material:
 		case T_Sort:
+		case T_CustomPlanMarkPos:
 			return true;
 
 		case T_Result:
@@ -465,6 +479,15 @@ ExecSupportsBackwardScan(Plan *node)
 			return ExecSupportsBackwardScan(((SubqueryScan *) node)->subplan) &&
 				TargetListSupportsBackwardScan(node->targetlist);
 
+		case T_CustomPlan:
+			{
+				CustomPlan *cplan = (CustomPlan *) node;
+
+				if (cplan->methods->SupportBackwardScan)
+					return cplan->methods->SupportBackwardScan(cplan);
+			}
+			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..62ebab9 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,12 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
 													   estate, eflags);
 			break;
 
+		case T_CustomPlan:
+		case T_CustomPlanMarkPos:
+			result = (PlanState *) ExecInitCustomPlan((CustomPlan *) node,
+													  estate, eflags);
+			break;
+
 			/*
 			 * join nodes
 			 */
@@ -442,6 +449,10 @@ ExecProcNode(PlanState *node)
 			result = ExecForeignScan((ForeignScanState *) node);
 			break;
 
+		case T_CustomPlanState:
+			result = ExecCustomPlan((CustomPlanState *) node);
+			break;
+
 			/*
 			 * join nodes
 			 */
@@ -558,6 +569,10 @@ MultiExecProcNode(PlanState *node)
 			result = MultiExecBitmapOr((BitmapOrState *) node);
 			break;
 
+		case T_CustomPlanState:
+			result = MultiExecCustomPlan((CustomPlanState *) node);
+			break;
+
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
 			result = NULL;
@@ -678,6 +693,10 @@ ExecEndNode(PlanState *node)
 			ExecEndForeignScan((ForeignScanState *) node);
 			break;
 
+		case T_CustomPlanState:
+			ExecEndCustomPlan((CustomPlanState *) node);
+			break;
+
 			/*
 			 * join nodes
 			 */
diff --git a/src/backend/executor/nodeCustom.c b/src/backend/executor/nodeCustom.c
new file mode 100644
index 0000000..55502fd
--- /dev/null
+++ b/src/backend/executor/nodeCustom.c
@@ -0,0 +1,135 @@
+/* ------------------------------------------------------------------------
+ *
+ * nodeCustom.c
+ *		Routines to handle execution of custom plan 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"
+
+CustomPlanState *
+ExecInitCustomPlan(CustomPlan *cplan, EState *estate, int eflags)
+{
+	CustomPlanState    *cps = cplan->methods->CreateCustomPlanState(cplan);
+
+	/* fill up fields of PlanState */
+	cps->ss.ps.plan = &cplan->scan.plan;
+	cps->ss.ps.state = estate;
+
+	/* create expression context for node */
+	ExecAssignExprContext(estate, &cps->ss.ps);
+	cps->ss.ps.ps_TupFromTlist = false;
+
+	/* initialize child expressions */
+	cps->ss.ps.targetlist = (List *)
+		ExecInitExpr((Expr *) cplan->scan.plan.targetlist,
+					 (PlanState *) cps);
+	cps->ss.ps.qual = (List *)
+		ExecInitExpr((Expr *) cplan->scan.plan.qual,
+					 (PlanState *) cps);
+
+	/* initialization of result tuple slot  */
+	ExecInitResultTupleSlot(estate, &cps->ss.ps);
+	ExecAssignResultTypeFromTL(&cps->ss.ps);
+
+	if (cplan->scan.scanrelid > 0)
+	{
+		Relation	heap_rel;
+
+		heap_rel = ExecOpenScanRelation(estate, cplan->scan.scanrelid, eflags);
+		cps->ss.ss_currentRelation = heap_rel;
+		cps->ss.ss_currentScanDesc = NULL;	/* set by provider on demand */
+		ExecInitScanTupleSlot(estate, &cps->ss);
+		ExecAssignScanType(&cps->ss, RelationGetDescr(heap_rel));
+		ExecAssignScanProjectionInfo(&cps->ss);
+	}
+	else
+	{
+		/*
+		 * Elsewhere, custom-plan provider should be responsible to put
+		 * appropriate initialization of scan tuple-slot and projection
+		 * info.
+		 */
+		cps->ss.ss_currentRelation = NULL;
+		cps->ss.ss_currentScanDesc = NULL;
+		cps->ss.ss_ScanTupleSlot = NULL;
+		cps->ss.ps.ps_ProjInfo = NULL;
+	}
+	/*
+	 * Then, custom-plan provider can have all the own original
+	 * initialization on demand.
+	 */
+	cps->methods->BeginCustomPlan(cps, estate, eflags);
+
+	return cps;
+}
+
+TupleTableSlot *
+ExecCustomPlan(CustomPlanState *cpstate)
+{
+	Assert(cpstate->methods->ExecCustomPlan != NULL);
+	return cpstate->methods->ExecCustomPlan(cpstate);
+}
+
+Node *
+MultiExecCustomPlan(CustomPlanState *cpstate)
+{
+	if (!cpstate->methods->MultiExecCustomPlan)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("CustomPlan \"%s\" does not support MultiExec method",
+						cpstate->methods->CustomName)));
+	return cpstate->methods->MultiExecCustomPlan(cpstate);
+}
+
+void
+ExecEndCustomPlan(CustomPlanState *cpstate)
+{
+	Assert(cpstate->methods->EndCustomPlan != NULL);
+	cpstate->methods->EndCustomPlan(cpstate);
+
+	/* Free the exprcontext */
+	ExecFreeExprContext(&cpstate->ss.ps);
+
+	/* Clean out the tuple table */
+	ExecClearTuple(cpstate->ss.ps.ps_ResultTupleSlot);
+	if (cpstate->ss.ss_ScanTupleSlot)
+		ExecClearTuple(cpstate->ss.ss_ScanTupleSlot);
+
+	/* Close the heap relation, if needed */
+	if (cpstate->ss.ss_currentRelation)
+		ExecCloseScanRelation(cpstate->ss.ss_currentRelation);
+}
+
+void
+ExecReScanCustomPlan(CustomPlanState *cpstate)
+{
+	Assert(cpstate->methods->ReScanCustomPlan != NULL);
+	cpstate->methods->ReScanCustomPlan(cpstate);
+}
+
+void
+ExecCustomMarkPos(CustomPlanState *cpstate)
+{
+	Assert(cpstate->methods->MarkPosCustomPlan != NULL);
+	cpstate->methods->MarkPosCustomPlan(cpstate);
+}
+
+void
+ExecCustomRestrPos(CustomPlanState *cpstate)
+{
+	Assert(cpstate->methods->RestrPosCustomPlan != NULL);
+	cpstate->methods->RestrPosCustomPlan(cpstate);
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 43530aa..a10599a 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -598,6 +598,21 @@ _copyForeignScan(const ForeignScan *from)
 }
 
 /*
+ * _copyCustomPlan
+ */
+static CustomPlan *
+_copyCustomPlan(const CustomPlan *from)
+{
+	CustomPlan *newnode;
+
+	newnode = from->methods->CopyCustomPlan(from);
+	Assert(nodeTag(newnode) == nodeTag(from));
+	CopyScanFields((const Scan *) from, (Scan *) newnode);
+
+	return newnode;
+}
+
+/*
  * CopyJoinFields
  *
  *		This function copies the fields of the Join node.  It is used by
@@ -3821,6 +3836,18 @@ _copyAlterTSConfigurationStmt(const AlterTSConfigurationStmt *from)
 	return newnode;
 }
 
+static CreateCustomPlanStmt *
+_copyCreateCustomPlanStmt(const CreateCustomPlanStmt *from)
+{
+	CreateCustomPlanStmt *newnode = makeNode(CreateCustomPlanStmt);
+
+	COPY_STRING_FIELD(custom_name);
+	COPY_SCALAR_FIELD(custom_class);
+	COPY_NODE_FIELD(custom_options);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					pg_list.h copy functions
  * ****************************************************************
@@ -3984,6 +4011,10 @@ copyObject(const void *from)
 		case T_ForeignScan:
 			retval = _copyForeignScan(from);
 			break;
+		case T_CustomPlan:
+		case T_CustomPlanMarkPos:
+			retval = _copyCustomPlan(from);
+			break;
 		case T_Join:
 			retval = _copyJoin(from);
 			break;
@@ -4530,6 +4561,9 @@ copyObject(const void *from)
 		case T_AlterTSConfigurationStmt:
 			retval = _copyAlterTSConfigurationStmt(from);
 			break;
+		case T_CreateCustomPlanStmt:
+			retval = _copyCreateCustomPlanStmt(from);
+			break;
 
 		case T_A_Expr:
 			retval = _copyAExpr(from);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 2407cb7..fc0165e 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1994,6 +1994,17 @@ _equalAlterTSConfigurationStmt(const AlterTSConfigurationStmt *a,
 }
 
 static bool
+_equalCreateCustomPlanStmt(const CreateCustomPlanStmt *a,
+						   const CreateCustomPlanStmt *b)
+{
+	COMPARE_STRING_FIELD(custom_name);
+	COMPARE_SCALAR_FIELD(custom_class);
+	COMPARE_NODE_FIELD(custom_options);
+
+	return true;
+}
+
+static bool
 _equalAExpr(const A_Expr *a, const A_Expr *b)
 {
 	COMPARE_SCALAR_FIELD(kind);
@@ -2998,6 +3009,9 @@ equal(const void *a, const void *b)
 		case T_AlterTSConfigurationStmt:
 			retval = _equalAlterTSConfigurationStmt(a, b);
 			break;
+		case T_CreateCustomPlanStmt:
+			retval = _equalCreateCustomPlanStmt(a, b);
+			break;
 
 		case T_A_Expr:
 			retval = _equalAExpr(a, b);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 11c7486..bf3953d 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -564,6 +564,22 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
 }
 
 static void
+_outCustomPlan(StringInfo str, const CustomPlan *node)
+{
+	if (IsA(node, CustomPlan))
+		WRITE_NODE_TYPE("CUSTOMPLAN");
+	else if (IsA(node, CustomPlanMarkPos))
+		WRITE_NODE_TYPE("CUSTOMPLANMARKPOS");
+	else
+		elog(ERROR, "unexpected node tag given: %d", (int)nodeTag(node));
+
+	_outScanInfo(str, (const Scan *) node);
+	appendStringInfo(str, " :methods");
+	_outToken(str, node->methods->CustomName);
+	node->methods->TextOutCustomPlan(str, node);
+}
+
+static void
 _outJoin(StringInfo str, const Join *node)
 {
 	WRITE_NODE_TYPE("JOIN");
@@ -1581,6 +1597,16 @@ _outForeignPath(StringInfo str, const ForeignPath *node)
 }
 
 static void
+_outCustomPath(StringInfo str, const CustomPath *node)
+{
+	WRITE_NODE_TYPE("CUSTOMPATH");
+	_outPathInfo(str, (const Path *) node);
+	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");
@@ -2829,6 +2855,10 @@ _outNode(StringInfo str, const void *obj)
 			case T_ForeignScan:
 				_outForeignScan(str, obj);
 				break;
+			case T_CustomPlan:
+			case T_CustomPlanMarkPos:
+				_outCustomPlan(str, obj);
+				break;
 			case T_Join:
 				_outJoin(str, obj);
 				break;
@@ -3037,6 +3067,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 fdaa964..bf95408 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -326,7 +326,7 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 				}
 				break;
 			case RTE_SUBQUERY:
-				/* Subquery --- fully handled during set_rel_size */
+				/* Subquery --- path was added during set_rel_size */
 				break;
 			case RTE_FUNCTION:
 				/* RangeFunction */
@@ -337,12 +337,17 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 				set_values_pathlist(root, rel, rte);
 				break;
 			case RTE_CTE:
-				/* CTE reference --- fully handled during set_rel_size */
+				/* CTE reference --- path was added during set_rel_size */
 				break;
 			default:
 				elog(ERROR, "unexpected rtekind: %d", (int) rel->rtekind);
 				break;
 		}
+		/* Also, consider paths by custom-plan providers */
+		call_custom_scan_providers(root, rel, rte);
+
+		/* Select cheapest path */
+		set_cheapest(rel);
 	}
 
 #ifdef OPTIMIZER_DEBUG
@@ -391,9 +396,6 @@ set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 
 	/* Consider TID scans */
 	create_tidscan_paths(root, rel);
-
-	/* Now find the cheapest of the paths for this rel */
-	set_cheapest(rel);
 }
 
 /*
@@ -419,9 +421,6 @@ set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 {
 	/* Call the FDW's GetForeignPaths function to generate path(s) */
 	rel->fdwroutine->GetForeignPaths(root, rel, rte->relid);
-
-	/* Select cheapest path */
-	set_cheapest(rel);
 }
 
 /*
@@ -1256,9 +1255,6 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
 
 	/* Generate appropriate path */
 	add_path(rel, create_subqueryscan_path(root, rel, pathkeys, required_outer));
-
-	/* Select cheapest path (pretty easy in this case...) */
-	set_cheapest(rel);
 }
 
 /*
@@ -1327,9 +1323,6 @@ set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 	/* Generate appropriate path */
 	add_path(rel, create_functionscan_path(root, rel,
 										   pathkeys, required_outer));
-
-	/* Select cheapest path (pretty easy in this case...) */
-	set_cheapest(rel);
 }
 
 /*
@@ -1350,9 +1343,6 @@ set_values_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 
 	/* Generate appropriate path */
 	add_path(rel, create_valuesscan_path(root, rel, required_outer));
-
-	/* Select cheapest path (pretty easy in this case...) */
-	set_cheapest(rel);
 }
 
 /*
@@ -1419,9 +1409,6 @@ set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 
 	/* Generate appropriate path */
 	add_path(rel, create_ctescan_path(root, rel, required_outer));
-
-	/* Select cheapest path (pretty easy in this case...) */
-	set_cheapest(rel);
 }
 
 /*
@@ -1472,9 +1459,6 @@ set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 
 	/* Generate appropriate path */
 	add_path(rel, create_worktablescan_path(root, rel, required_outer));
-
-	/* Select cheapest path (pretty easy in this case...) */
-	set_cheapest(rel);
 }
 
 /*
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 4b641a2..cb07af4 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -77,13 +77,13 @@ 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 Plan *create_custom_plan(PlannerInfo *root, CustomPath *best_path);
 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,
 					  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);
@@ -261,6 +261,9 @@ create_plan_recurse(PlannerInfo *root, Path *best_path)
 			plan = create_unique_plan(root,
 									  (UniquePath *) best_path);
 			break;
+		case T_CustomPlan:
+			plan = create_custom_plan(root, (CustomPath *) best_path);
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) best_path->pathtype);
@@ -1072,6 +1075,96 @@ 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 'scan_clauses' and targetlist 'tlist'.
+ */
+static Plan *
+create_custom_plan(PlannerInfo *root, CustomPath *best_path)
+{
+	CustomPlan	   *custom_plan;
+	RelOptInfo	   *rel;
+	List		   *tlist = NIL;
+	List		   *clauses = NIL;
+
+	/*
+	 * Create a custom-plan object delivered from CustomPlan type,
+	 * according to the supplied CustomPath
+	 */
+	Assert(best_path->path.pathtype == T_CustomPlan ||
+		   best_path->path.pathtype == T_CustomPlanMarkPos);
+	custom_plan = best_path->methods->CreateCustomPlan(root, best_path);
+	Assert(nodeTag(custom_plan) == best_path->path.pathtype);
+
+	rel = best_path->path.parent;
+	if (rel)
+	{
+		if (rel->reloptkind == RELOPT_BASEREL)
+		{
+			Assert(rel->relid > 0);
+			custom_plan->scan.scanrelid = rel->relid;
+
+			/*
+			 * For table scans, rather than using the relation targetlist
+			 * (which is only those Vars actually needed by the query),
+			 * we prefer to generate a tlist containing all Vars in order.
+			 * This will allow the executor to optimize away projection of
+			 * the table tuples, if possible.
+			 */
+			if (use_physical_tlist(root, rel))
+				tlist = build_physical_tlist(root, rel);
+		}
+		/* elsewhere, we generate a tlist from the relation targetlist */
+		if (tlist == NIL)
+			tlist = build_path_tlist(root, &best_path->path);
+
+		/*
+		 * Extract the relevant restriction clauses from the parent relation.
+		 * The executor must apply all these restrictions during the scan,
+		 * except for pseudoconstants which we'll take care of below.
+		 */
+		clauses = rel->baserestrictinfo;
+
+		/*
+		 * If this is a parameterized scan, we also need to enforce all
+		 * the join clauses available from the outer relation(s).
+		 */
+		if (best_path->path.param_info)
+			clauses = list_concat(list_copy(clauses),
+								  best_path->path.param_info->ppi_clauses);
+
+		/* Sort clauses into best execution order */
+		clauses = order_qual_clauses(root, clauses);
+
+		/*
+		 * Replace outer-relation variables with nestloop params.
+		 * Note that any other clauses which is managed by extension
+		 * itself has to be handled in InitCustomPlan() method, as
+		 * built-in code doing.
+		 */
+		if (best_path->path.param_info)
+			clauses = (List *)replace_nestloop_params(root, (Node *)clauses);
+	}
+	/*
+	 * Copy cost data from Path to Plan; no need to make custom-plan
+	 * providers do this
+	 */
+	copy_path_costsize((Plan *)custom_plan, (Path *)best_path);
+
+	/*
+	 * Let the custom-plan provider perform its final initialization
+	 * of this CustomPlan (to be an inherited type, actually) node
+	 * according to its own necessity.
+	 * Note that custom-plan provider may/can replace (or stack another
+	 * one on) its own custom-plan node on demand, for example, to add
+	 * Result node to handle pseudo constant using create_gating_plan().
+	 */
+	return custom_plan->methods->InitCustomPlan(custom_plan,
+												root, best_path,
+												tlist, clauses);
+}
 
 /*****************************************************************************
  *
@@ -2540,7 +2633,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 768c5c7..627946e 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);
@@ -576,6 +575,14 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 			}
 			break;
 
+		case T_CustomPlan:
+			{
+				CustomPlan *cplan = (CustomPlan *) plan;
+
+				cplan->methods->SetCustomPlanRef(root, cplan, rtoffset);
+			}
+			break;
+
 		case T_NestLoop:
 		case T_MergeJoin:
 		case T_HashJoin:
@@ -1126,7 +1133,7 @@ fix_expr_common(PlannerInfo *root, Node *node)
  * 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 be92049..070634f 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -2236,6 +2236,21 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
 			context.paramids = bms_add_members(context.paramids, scan_params);
 			break;
 
+		case T_CustomPlan:
+			{
+				CustomPlan *cplan = (CustomPlan *) plan;
+
+				context.paramids =
+					cplan->methods->FinalizeCustomPlan(root,
+													   cplan,
+													   context.paramids,
+													   valid_params,
+													   scan_params,
+													   finalize_primnode,
+													   (void *)&context);
+			}
+			break;
+
 		case T_ModifyTable:
 			{
 				ModifyTable *mtplan = (ModifyTable *) plan;
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 4e05dcd..1e4150f 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -16,6 +16,10 @@
 
 #include <math.h>
 
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "catalog/pg_custom_plan.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
@@ -25,8 +29,11 @@
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
+#include "utils/inval.h"
 #include "utils/lsyscache.h"
+#include "utils/memutils.h"
 #include "utils/selfuncs.h"
+#include "utils/syscache.h"
 
 
 typedef enum
@@ -2067,3 +2074,106 @@ reparameterize_path(PlannerInfo *root, Path *path,
 	}
 	return NULL;
 }
+
+/*****************************************************************************
+ *		creation of custom-plan paths
+ *****************************************************************************/
+static List *custom_scan_callchain = NIL;
+static bool custom_plan_callchain_is_ready = false;
+static MemoryContext custom_plan_memcxt = NULL;
+
+static void
+invalidate_custom_plan_callchain(Datum arg, int cacheid, uint32 hashvalue)
+{
+	MemoryContextReset(custom_plan_memcxt);
+	custom_plan_callchain_is_ready = false;
+	custom_scan_callchain = NIL;
+}
+
+static void
+setup_custom_plan_callchain(void)
+{
+	Relation		rel;
+	SysScanDesc		scan;
+	HeapTuple		tuple;
+	MemoryContext	oldcxt;
+
+	custom_scan_callchain = NIL;
+
+	rel = heap_open(CustomPlanRelationId, AccessShareLock);
+
+	/* full scan on the pg_custom_plan once */
+	scan = systable_beginscan(rel, InvalidOid, false, NULL, 0, NULL);
+
+	oldcxt = MemoryContextSwitchTo(custom_plan_memcxt);
+	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+	{
+		Form_pg_custom_plan	custForm
+			= (Form_pg_custom_plan) GETSTRUCT(tuple);
+
+		if ((custForm->custclass & CUSTOMPLAN_CLASS_SCAN) != 0)
+		{
+			custom_scan_callchain = lappend_oid(custom_scan_callchain,
+												custForm->custprovider);
+		}
+		else
+			elog(LOG, "Bug? custom-plan \"%s\" has unknown class: %c",
+				 NameStr(custForm->custname), custForm->custclass);
+	}
+	MemoryContextSwitchTo(oldcxt);
+	systable_endscan(scan);
+
+	heap_close(rel, AccessShareLock);
+
+	custom_plan_callchain_is_ready = true;
+}
+
+static void
+init_custom_plan_callchain(void)
+{
+	/* memory context to keep callchain for custom-plans */
+	custom_plan_memcxt = AllocSetContextCreate(CacheMemoryContext,
+											   "custom plan memory context",
+											   ALLOCSET_DEFAULT_MINSIZE,
+											   ALLOCSET_DEFAULT_INITSIZE,
+											   ALLOCSET_DEFAULT_MAXSIZE);
+
+	/* flush cached callchain on catalog updates */
+	CacheRegisterSyscacheCallback(CUSTOMPLANOID,
+								  invalidate_custom_plan_callchain,
+								  (Datum) 0);
+	/* also, initial setting up */
+	setup_custom_plan_callchain();
+}
+
+/*
+ * call_custom_scan_providers
+ *
+ * A callchain on relation scan. custom-plan provider can add alternative
+ * scan paths delivered from CustomPath class.
+ */
+void
+call_custom_scan_providers(PlannerInfo *root,
+						   RelOptInfo *baserel,
+						   RangeTblEntry *rte)
+{
+	customScanArg	sarg;
+	ListCell	   *cell;
+
+	if (!custom_plan_memcxt)
+		init_custom_plan_callchain();
+	else if (!custom_plan_callchain_is_ready)
+		setup_custom_plan_callchain();
+
+	Assert(custom_plan_callchain_is_ready);
+	sarg.custom_class = CUSTOMPLAN_CLASS_SCAN;
+	sarg.root = root;
+	sarg.baserel = baserel;
+	sarg.rte = rte;
+
+	foreach (cell, custom_scan_callchain)
+	{
+		(void) OidFunctionCall1(lfirst_oid(cell),
+								PointerGetDatum(&sarg));
+	}
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7b9895d..6881484 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -51,6 +51,7 @@
 
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/pg_custom_plan.h"
 #include "catalog/pg_trigger.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -248,6 +249,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		DropOwnedStmt ReassignOwnedStmt
 		AlterTSConfigurationStmt AlterTSDictionaryStmt
 		CreateMatViewStmt RefreshMatViewStmt
+		CreateCustomPlanStmt DropCustomPlanStmt
 
 %type <node>	select_no_parens select_with_parens select_clause
 				simple_select values_clause
@@ -500,6 +502,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
 
+%type <ival>	custom_class
+%type <defelt>	custom_option
+%type <list>	opt_custom_options custom_options
+
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -535,7 +541,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
 	CROSS CSV CURRENT_P
 	CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
-	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
+	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CUSTOM CYCLE
 
 	DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
 	DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DESC
@@ -574,8 +580,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	OBJECT_P OF OFF OFFSET OIDS ON ONLY OPERATOR OPTION OPTIONS OR
 	ORDER ORDINALITY OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER
 
-	PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POSITION
-	PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
+	PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLAN PLANS POSITION
+	PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY PROVIDER
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROGRAM
 
 	QUOTE
@@ -585,8 +591,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK
 	ROW ROWS RULE
 
-	SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
-	SERIALIZABLE SERVER SESSION SESSION_USER SET SETOF SHARE
+	SAVEPOINT SCAN SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE
+	SEQUENCES SERIALIZABLE SERVER SESSION SESSION_USER SET SETOF SHARE
 	SHOW SIMILAR SIMPLE SMALLINT SNAPSHOT SOME STABLE STANDALONE_P START
 	STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBSTRING
 	SYMMETRIC SYSID SYSTEM_P
@@ -748,6 +754,7 @@ stmt :
 			| CreateAssertStmt
 			| CreateCastStmt
 			| CreateConversionStmt
+			| CreateCustomPlanStmt
 			| CreateDomainStmt
 			| CreateExtensionStmt
 			| CreateFdwStmt
@@ -778,6 +785,7 @@ stmt :
 			| DoStmt
 			| DropAssertStmt
 			| DropCastStmt
+			| DropCustomPlanStmt
 			| DropFdwStmt
 			| DropForeignServerStmt
 			| DropGroupStmt
@@ -8676,6 +8684,77 @@ CreateConversionStmt:
 			}
 		;
 
+/****************************************************************************
+ *
+ *	QUERY:
+ *			CREATE CUSTOM PLAN name <options>
+ *
+ ****************************************************************************/
+
+CreateCustomPlanStmt:
+			CREATE CUSTOM PLAN name FOR custom_class opt_custom_options
+			{
+				CreateCustomPlanStmt *n = makeNode(CreateCustomPlanStmt);
+				n->custom_name = $4;
+				n->custom_class = $6;
+				n->custom_options = $7;
+				$$ = (Node *) n;
+			}
+		;
+
+custom_class:
+			SCAN				{ $$ = CUSTOMPLAN_CLASS_SCAN; }
+		;
+
+custom_option:
+			PROVIDER handler_name
+			{
+				$$ = makeDefElem("provider", (Node *)$2);
+			}
+		;
+
+custom_options:
+			custom_option					{ $$ = list_make1($1); }
+			| custom_options custom_option	{ $$ = lappend($1, $2); }
+		;
+
+opt_custom_options:
+			custom_options		{ $$ = $1; }
+			| /* empty */		{ $$ = NIL; }
+		;
+
+/****************************************************************************
+ *
+ *     QUERY:
+ *             DROP CUSTOM PLAN name
+ *
+ ****************************************************************************/
+
+DropCustomPlanStmt:
+			DROP CUSTOM PLAN name opt_drop_behavior
+			{
+				DropStmt *n = makeNode(DropStmt);
+				n->removeType = OBJECT_CUSTOM_PLAN;
+				n->objects = list_make1(list_make1(makeString($4)));
+				n->arguments = NIL;
+				n->missing_ok = false;
+				n->behavior = $5;
+				n->concurrent = false;
+				$$ = (Node *) n;
+			}
+		|	DROP CUSTOM PLAN IF_P EXISTS name opt_drop_behavior
+			{
+				DropStmt *n = makeNode(DropStmt);
+				n->removeType = OBJECT_CUSTOM_PLAN;
+				n->objects = list_make1(list_make1(makeString($6)));
+				n->arguments = NIL;
+				n->missing_ok = true;
+				n->behavior = $7;
+				n->concurrent = false;
+				$$ = (Node *) n;
+			}
+		;
+
 /*****************************************************************************
  *
  *		QUERY:
@@ -12842,6 +12921,7 @@ unreserved_keyword:
 			| CSV
 			| CURRENT_P
 			| CURSOR
+			| CUSTOM
 			| CYCLE
 			| DATA_P
 			| DATABASE
@@ -12954,6 +13034,7 @@ unreserved_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PLAN
 			| PLANS
 			| PRECEDING
 			| PREPARE
@@ -12964,6 +13045,7 @@ unreserved_keyword:
 			| PROCEDURAL
 			| PROCEDURE
 			| PROGRAM
+			| PROVIDER
 			| QUOTE
 			| RANGE
 			| READ
@@ -12989,6 +13071,7 @@ unreserved_keyword:
 			| ROWS
 			| RULE
 			| SAVEPOINT
+			| SCAN
 			| SCHEMA
 			| SCROLL
 			| SEARCH
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 3423898..3aa7d5f 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -202,6 +202,7 @@ check_xact_readonly(Node *parsetree)
 		case T_AlterTableSpaceOptionsStmt:
 		case T_AlterTableSpaceMoveStmt:
 		case T_CreateForeignTableStmt:
+		case T_CreateCustomPlanStmt:
 		case T_SecLabelStmt:
 			PreventCommandIfReadOnly(CreateCommandTag(parsetree));
 			break;
@@ -683,6 +684,10 @@ standard_ProcessUtility(Node *parsetree,
 			AlterEventTrigger((AlterEventTrigStmt *) parsetree);
 			break;
 
+		case  T_CreateCustomPlanStmt:
+			DefineCustomPlan((CreateCustomPlanStmt *) parsetree);
+			break;
+
 			/*
 			 * ******************************** ROLE statements ****
 			 */
@@ -1940,6 +1945,9 @@ CreateCommandTag(Node *parsetree)
 				case OBJECT_OPFAMILY:
 					tag = "DROP OPERATOR FAMILY";
 					break;
+				case OBJECT_CUSTOM_PLAN:
+					tag = "DROP CUSTOM PLAN";
+					break;
 				default:
 					tag = "???";
 			}
@@ -2207,6 +2215,10 @@ CreateCommandTag(Node *parsetree)
 			tag = "ALTER EVENT TRIGGER";
 			break;
 
+		case T_CreateCustomPlanStmt:
+			tag = "CREATE CUSTOM PLAN";
+			break;
+
 		case T_CreatePLangStmt:
 			tag = "CREATE LANGUAGE";
 			break;
@@ -2830,6 +2842,10 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_CreateCustomPlanStmt:
+			lev = LOGSTMT_DDL;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index a30d8fe..f4c5d92 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -5387,6 +5387,24 @@ 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-plan provider replaced a varno in expression tree by special
+ * varno.
+ */
+static Node *
+GetSpecialCustomVar(PlanState *ps, Var *varnode)
+{
+	CustomPlanState *cps = (CustomPlanState *) ps;
+
+	Assert(IsA(ps, CustomPlanState));
+	Assert(IS_SPECIAL_VARNO(varnode->varno));
+
+	return (Node *)cps->methods->GetSpecialCustomVar(cps, varnode);
+}
 
 /*
  * Display a Var appropriately.
@@ -5416,6 +5434,7 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
 	int			netlevelsup;
 	deparse_namespace *dpns;
 	deparse_columns *colinfo;
+	Node	   *expr;
 	char	   *refname;
 	char	   *attname;
 
@@ -5440,6 +5459,21 @@ 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, CustomPlanState) &&
+			 (expr = GetSpecialCustomVar(dpns->planstate, var)) != NULL)
+	{
+		/*
+		 * 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, ')');
+		return NULL;
+	}
 	else if (var->varno == OUTER_VAR && dpns->outer_tlist)
 	{
 		TargetEntry *tle;
@@ -5728,6 +5762,26 @@ 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, CustomPlanState) &&
+			 (expr = GetSpecialCustomVar(dpns->planstate, var)) != NULL)
+	{
+		StringInfo		saved = context->buf;
+		StringInfoData	temp;
+
+		initStringInfo(&temp);
+		context->buf = &temp;
+
+		if (!IsA(expr, Var))
+			appendStringInfoChar(context->buf, '(');
+		get_rule_expr((Node *) expr, context, true);
+		if (!IsA(expr, Var))
+			appendStringInfoChar(context->buf, ')');
+
+		context->buf = saved;
+
+		return temp.data;
+	}
 	else if (var->varno == OUTER_VAR && dpns->outer_tlist)
 	{
 		TargetEntry *tle;
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 94d951c..20d7767 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -32,6 +32,7 @@
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_conversion.h"
+#include "catalog/pg_custom_plan.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_db_role_setting.h"
 #include "catalog/pg_default_acl.h"
@@ -345,6 +346,28 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		8
 	},
+	{CustomPlanRelationId,		/* CUSTOMPLANOID */
+		CustomPlanOidIndexId,
+		1,
+		{
+			ObjectIdAttributeNumber,
+			0,
+			0,
+			0
+		},
+		32
+	},
+	{CustomPlanRelationId,		/* CUSTOMPLANNAME */
+		CustomPlanNameIndexId,
+		1,
+		{
+			Anum_pg_custom_plan_custname,
+			0,
+			0,
+			0,
+		},
+		32
+	},
 	{DatabaseRelationId,		/* DATABASEOID */
 		DatabaseOidIndexId,
 		1,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 8ed2592..56f5371 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -147,6 +147,7 @@ typedef enum ObjectClass
 	OCLASS_DEFACL,				/* pg_default_acl */
 	OCLASS_EXTENSION,			/* pg_extension */
 	OCLASS_EVENT_TRIGGER,		/* pg_event_trigger */
+	OCLASS_CUSTOM_PLAN,			/* pg_custom_plan */
 	MAX_OCLASS					/* MUST BE LAST */
 } ObjectClass;
 
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 0515b75..a5e6cea 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -313,6 +313,12 @@ DECLARE_UNIQUE_INDEX(pg_extension_name_index, 3081, on pg_extension using btree(
 DECLARE_UNIQUE_INDEX(pg_range_rngtypid_index, 3542, on pg_range using btree(rngtypid oid_ops));
 #define RangeTypidIndexId					3542
 
+DECLARE_UNIQUE_INDEX(pg_custom_plan_oid_index, 3563, on pg_custom_plan using btree(oid oid_ops));
+#define CustomPlanOidIndexId 3563
+
+DECLARE_UNIQUE_INDEX(pg_custom_plan_name_index, 3564, on pg_custom_plan using btree(custname name_ops));
+#define CustomPlanNameIndexId 3564
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_custom_plan.h b/src/include/catalog/pg_custom_plan.h
new file mode 100644
index 0000000..5b31a72
--- /dev/null
+++ b/src/include/catalog/pg_custom_plan.h
@@ -0,0 +1,49 @@
+/* -------------------------------------------------------------------------
+ *
+ * pg_custom_plan.h
+ *	definition of the system "custom plan" relation (pg_custom_plan)
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * -------------------------------------------------------------------------
+ */
+#ifndef PG_CUSTOM_PLAN_H
+#define PG_CUSTOM_PLAN_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *     pg_custom_plan definition.  cpp turns this into
+ *     typedef struct FormData_pg_custom_plan
+ * ----------------
+ */
+#define CustomPlanRelationId       3562
+
+CATALOG(pg_custom_plan,3562)
+{
+	NameData	custname;		/* name of custom-plan provider */
+	char		custclass;		/* class of custom-plan */
+	Oid			custprovider;	/* function of custom-plan provider */
+} FormData_pg_custom_plan;
+
+/* ----------------
+ *     Form_pg_custom_plan corresponds to a pointer to a tuple
+ *     with the format of pg_custom_plan relation.
+ * ----------------
+ */
+typedef FormData_pg_custom_plan *Form_pg_custom_plan;
+
+/* ----------------
+ *     compiler constants for pg_custom_plan
+ * ----------------
+ */
+#define Natts_pg_custom_plan				3
+#define Anum_pg_custom_plan_custname		1
+#define Anum_pg_custom_plan_custclass		2
+#define Anum_pg_custom_plan_custprovider	3
+
+/* definition of custclass */
+#define CUSTOMPLAN_CLASS_SCAN			's'
+
+#endif	/* PG_CUSTOM_PLAN_H */
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index 87ee4eb..942c22a 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");
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 5ec9374..ee36614 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -129,6 +129,11 @@ extern Datum transformGenericOptions(Oid catalogId,
 						List *options,
 						Oid fdwvalidator);
 
+/* commands/custom_plan.c */
+extern Oid get_custom_plan_oid(const char *custom_name, bool missing_ok);
+extern void	RemoveCustomPlanById(Oid cust_oid);
+extern Oid	DefineCustomPlan(CreateCustomPlanStmt *stmt);
+
 /* support routines in commands/define.c */
 
 extern char *defGetString(DefElem *def);
diff --git a/src/include/executor/nodeCustom.h b/src/include/executor/nodeCustom.h
new file mode 100644
index 0000000..50a4edb
--- /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 CustomPlanState *ExecInitCustomPlan(CustomPlan *cplan,
+										   EState *estate, int eflags);
+extern TupleTableSlot *ExecCustomPlan(CustomPlanState *node);
+extern Node *MultiExecCustomPlan(CustomPlanState *node);
+extern void ExecEndCustomPlan(CustomPlanState *node);
+
+extern void ExecReScanCustomPlan(CustomPlanState *node);
+extern void ExecCustomMarkPos(CustomPlanState *node);
+extern void ExecCustomRestrPos(CustomPlanState *node);
+
+#endif	/* NODECUSTOM_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 0ab2a13..06710a9 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1501,6 +1501,47 @@ typedef struct ForeignScanState
 	void	   *fdw_state;		/* foreign-data wrapper can keep state here */
 } ForeignScanState;
 
+/* ----------------
+ * CustomPlanState information
+ *
+ *		CustomPlan nodes are used to execute custom code within executor.
+ * ----------------
+ */
+struct CustomExecMethods;
+struct ExplainState;	/* to avoid to include explain.h here */
+
+typedef struct CustomPlanState
+{
+	ScanState	ss;
+	const struct CustomExecMethods *methods;
+} CustomPlanState;
+
+typedef struct CustomExecMethods
+{
+	const char     *CustomName;
+
+	/* EXECUTOR methods */
+	void    (*BeginCustomPlan)(CustomPlanState *node,
+							   EState *estate,
+							   int eflags);
+	TupleTableSlot *(*ExecCustomPlan)(CustomPlanState *node);
+	Node   *(*MultiExecCustomPlan)(CustomPlanState *node);
+	void	(*EndCustomPlan)(CustomPlanState *node);
+	void	(*ReScanCustomPlan)(CustomPlanState *node);
+	void	(*MarkPosCustomPlan)(CustomPlanState *node);
+	void	(*RestrPosCustomPlan)(CustomPlanState *node);
+
+	/* EXPLAIN support */
+	void	(*ExplainCustomPlanTargetRel)(CustomPlanState *node,
+										  struct ExplainState *es);
+	void    (*ExplainCustomPlan)(CustomPlanState *node,
+								 List *ancestors,
+								 struct ExplainState *es);
+	void	(*ExplainCustomPreScanNode)(CustomPlanState *node,
+										Bitmapset **rels_used);
+	Node   *(*GetSpecialCustomVar)(CustomPlanState *node, Var *varnode);
+} CustomExecMethods;
+
 /* ----------------------------------------------------------------
  *				 Join State Information
  * ----------------------------------------------------------------
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index bc58e16..99e9b2d 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -62,6 +62,8 @@ typedef enum NodeTag
 	T_CteScan,
 	T_WorkTableScan,
 	T_ForeignScan,
+	T_CustomPlan,
+	T_CustomPlanMarkPos,
 	T_Join,
 	T_NestLoop,
 	T_MergeJoin,
@@ -107,6 +109,7 @@ typedef enum NodeTag
 	T_CteScanState,
 	T_WorkTableScanState,
 	T_ForeignScanState,
+	T_CustomPlanState,
 	T_JoinState,
 	T_NestLoopState,
 	T_MergeJoinState,
@@ -224,6 +227,7 @@ typedef enum NodeTag
 	T_HashPath,
 	T_TidPath,
 	T_ForeignPath,
+	T_CustomPath,
 	T_AppendPath,
 	T_MergeAppendPath,
 	T_ResultPath,
@@ -365,6 +369,7 @@ typedef enum NodeTag
 	T_RefreshMatViewStmt,
 	T_ReplicaIdentityStmt,
 	T_AlterSystemStmt,
+	T_CreateCustomPlanStmt,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 7e560a1..d30c7d5 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1195,6 +1195,7 @@ typedef enum ObjectType
 	OBJECT_CONSTRAINT,
 	OBJECT_COLLATION,
 	OBJECT_CONVERSION,
+	OBJECT_CUSTOM_PLAN,
 	OBJECT_DATABASE,
 	OBJECT_DOMAIN,
 	OBJECT_EVENT_TRIGGER,
@@ -2036,6 +2037,18 @@ typedef struct AlterOpFamilyStmt
 } AlterOpFamilyStmt;
 
 /* ----------------------
+ *     Create Custom Plan Statement
+ * ----------------------
+ */
+typedef struct CreateCustomPlanStmt
+{
+	NodeTag     type;
+	char	   *custom_name;		/* name of custom-plan provider */
+	char		custom_class;		/* class of custom-plan provides */
+	List	   *custom_options;		/* generic options for provider */
+} CreateCustomPlanStmt;
+
+/* ----------------------
  *		Drop Table|Sequence|View|Index|Type|Domain|Conversion|Schema Statement
  * ----------------------
  */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 3b9c683..0b01580 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -15,6 +15,7 @@
 #define PLANNODES_H
 
 #include "access/sdir.h"
+#include "lib/stringinfo.h"
 #include "nodes/bitmapset.h"
 #include "nodes/primnodes.h"
 
@@ -479,6 +480,45 @@ typedef struct ForeignScan
 	bool		fsSystemCol;	/* true if any "system column" is needed */
 } ForeignScan;
 
+/* ----------------
+ *     CustomPlan node
+ * ----------------
+ */
+struct CustomPlanMethods;
+struct CustomPlanState;		/* to avoid to include nodes/execnodes.h here */
+struct CustomPath;			/* to avoid to include nodes/relation.h here */
+struct PlannerInfo;			/* to avoid to include nodes/relation.h here */
+
+typedef struct CustomPlan
+{
+	Scan		scan;
+	const struct CustomPlanMethods *methods;
+} CustomPlan;
+
+typedef struct CustomPlanMethods
+{
+	const char *CustomName;
+	Plan	  *(*InitCustomPlan)(CustomPlan *custom_plan,
+								 struct PlannerInfo *root,
+								 struct CustomPath *best_path,
+								 List *tlist,
+								 List *clauses);
+	void	   (*SetCustomPlanRef)(struct PlannerInfo *root,
+								   CustomPlan *custom_plan,
+								   int rtoffset);
+	bool	   (*SupportBackwardScan)(CustomPlan *custom_plan);
+	Bitmapset *(*FinalizeCustomPlan)(struct PlannerInfo *root,
+									 CustomPlan *custom_plan,
+									 Bitmapset *paramids,
+									 Bitmapset *valid_params,
+									 Bitmapset *scan_params,
+									 bool (*finalize_primnode)(),
+									 void *finalize_context);
+	struct CustomPlanState *(*CreateCustomPlanState)(CustomPlan *custom_plan);
+	void	   (*TextOutCustomPlan)(StringInfo str,
+									const CustomPlan *node);
+	CustomPlan *(*CopyCustomPlan)(const CustomPlan *from);
+} CustomPlanMethods;
 
 /*
  * ==========
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 300136e..b0e7c36 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"
@@ -878,6 +879,33 @@ 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;		/* to avoid to include plannode.h here */
+struct CustomPlan;	/* to avoid to include plannode.h here */
+
+typedef struct CustomPath
+{
+	Path        path;
+	const struct CustomPathMethods *methods;
+} CustomPath;
+
+typedef struct CustomPathMethods
+{
+	const char *CustomName;
+	struct CustomPlan *(*CreateCustomPlan)(PlannerInfo *root,
+										   CustomPath *best_path);
+	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..df4d6e8 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -129,6 +129,20 @@ extern Path *reparameterize_path(PlannerInfo *root, Path *path,
 					double loop_count);
 
 /*
+ * interface towards custom-plan provider functions
+ */
+typedef struct {
+	uint32			custom_class;
+	PlannerInfo	   *root;
+	RelOptInfo	   *baserel;
+	RangeTblEntry  *rte;
+} customScanArg;
+
+extern void call_custom_scan_providers(PlannerInfo *root,
+									   RelOptInfo *baserel,
+									   RangeTblEntry *rte);
+
+/*
  * prototypes for relnode.c
  */
 extern void setup_simple_rel_arrays(PlannerInfo *root);
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 8bdb7db..76e3c86 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
@@ -128,6 +129,7 @@ extern List *remove_useless_joins(PlannerInfo *root, List *joinlist);
  */
 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 set_opfuncid(OpExpr *opexpr);
 extern void set_sa_opfuncid(ScalarArrayOpExpr *opexpr);
 extern void record_plan_function_dependency(PlannerInfo *root, Oid funcid);
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 61fae22..9851fe9 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -107,6 +107,7 @@ PG_KEYWORD("current_time", CURRENT_TIME, RESERVED_KEYWORD)
 PG_KEYWORD("current_timestamp", CURRENT_TIMESTAMP, RESERVED_KEYWORD)
 PG_KEYWORD("current_user", CURRENT_USER, RESERVED_KEYWORD)
 PG_KEYWORD("cursor", CURSOR, UNRESERVED_KEYWORD)
+PG_KEYWORD("custom", CUSTOM, UNRESERVED_KEYWORD)
 PG_KEYWORD("cycle", CYCLE, UNRESERVED_KEYWORD)
 PG_KEYWORD("data", DATA_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("database", DATABASE, UNRESERVED_KEYWORD)
@@ -283,6 +284,7 @@ PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD)
 PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD)
+PG_KEYWORD("plan", PLAN, UNRESERVED_KEYWORD)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD)
 PG_KEYWORD("position", POSITION, COL_NAME_KEYWORD)
 PG_KEYWORD("preceding", PRECEDING, UNRESERVED_KEYWORD)
@@ -296,6 +298,7 @@ PG_KEYWORD("privileges", PRIVILEGES, UNRESERVED_KEYWORD)
 PG_KEYWORD("procedural", PROCEDURAL, UNRESERVED_KEYWORD)
 PG_KEYWORD("procedure", PROCEDURE, UNRESERVED_KEYWORD)
 PG_KEYWORD("program", PROGRAM, UNRESERVED_KEYWORD)
+PG_KEYWORD("provider", PROVIDER, UNRESERVED_KEYWORD)
 PG_KEYWORD("quote", QUOTE, UNRESERVED_KEYWORD)
 PG_KEYWORD("range", RANGE, UNRESERVED_KEYWORD)
 PG_KEYWORD("read", READ, UNRESERVED_KEYWORD)
@@ -326,6 +329,7 @@ PG_KEYWORD("row", ROW, COL_NAME_KEYWORD)
 PG_KEYWORD("rows", ROWS, UNRESERVED_KEYWORD)
 PG_KEYWORD("rule", RULE, UNRESERVED_KEYWORD)
 PG_KEYWORD("savepoint", SAVEPOINT, UNRESERVED_KEYWORD)
+PG_KEYWORD("scan", SCAN, UNRESERVED_KEYWORD)
 PG_KEYWORD("schema", SCHEMA, UNRESERVED_KEYWORD)
 PG_KEYWORD("scroll", SCROLL, UNRESERVED_KEYWORD)
 PG_KEYWORD("search", SEARCH, UNRESERVED_KEYWORD)
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index f97229f..7272eec 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -52,6 +52,8 @@ enum SysCacheIdentifier
 	CONNAMENSP,
 	CONSTROID,
 	CONVOID,
+	CUSTOMPLANOID,
+	CUSTOMPLANNAME,
 	DATABASEOID,
 	DEFACLROLENSPOBJ,
 	ENUMOID,
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 111d24c..e3cad45 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -97,6 +97,7 @@ pg_class|t
 pg_collation|t
 pg_constraint|t
 pg_conversion|t
+pg_custom_plan|t
 pg_database|t
 pg_db_role_setting|t
 pg_default_acl|t
#2Shigeru Hanada
shigeru.hanada@gmail.com
In reply to: Kohei KaiGai (#1)

Kaigai-san,

I've just applied v1 patch, and tried build and install, but I found two issues:

1) The contrib/ctidscan is not automatically built/installed because
it's not described in contrib/Makefile. Is this expected behavior?
2) I got an error message below when building document.

$ cd doc/src/sgml
$ make
openjade -wall -wno-unused-param -wno-empty -wfully-tagged -D . -D .
-d stylesheet.dsl -t sgml -i output-html -V html-index postgres.sgml
openjade:catalogs.sgml:2525:45:X: reference to non-existent ID
"SQL-CREATECUSTOMPLAN"
make: *** [HTML.index] Error 1
make: *** Deleting file `HTML.index'

I'll review another part of the patch, including the design.

2014-06-14 10:59 GMT+09:00 Kohei KaiGai <kaigai@kaigai.gr.jp>:

According to the discussion upthread, I revised the custom-plan patch
to focus on regular relation scan but no join support right now, and to
support DDL command to define custom-plan providers.

Planner integration with custom logic to scan a particular relation is
enough simple, unlike various join cases. It's almost similar to what
built-in logic are doing now - custom-plan provider adds a path node
with its cost estimation if it can offer alternative way to scan referenced
relation. (in case of no idea, it does not need to add any paths)

A new DDL syntax I'd like to propose is below:

CREATE CUSTOM PLAN <name> FOR <class> PROVIDER <function_name>;

<name> is as literal, put a unique identifier.
<class> is workload type to be offered by this custom-plan provider.
"scan" is the only option right now, that means base relation scan.
<function_name> is also as literal; it shall perform custom-plan provider.

A custom-plan provider function is assumed to take an argument of
"internal" type to deliver a set of planner information that is needed to
construct custom-plan pathnode.
In case of "scan" class, pointer towards an customScanArg object
shall be delivered on invocation of custom-plan provider.

typedef struct {
uint32 custom_class;
PlannerInfo *root;
RelOptInfo *baserel;
RangeTblEntry *rte;
} customScanArg;

In case when the custom-plan provider function being invoked thought
it can offer an alternative scan path on the relation of "baserel", things
to do is (1) construct a CustomPath (or its inherited data type) object
with a table of callback function pointers (2) put its own cost estimation,
and (3) call add_path() to register this path as an alternative one.

Once the custom-path was chosen by query planner, its CreateCustomPlan
callback is called to populate CustomPlan node based on the pathnode.
It also has a table of callback function pointers to handle various planner's
job in setrefs.c and so on.

Similarly, its CreateCustomPlanState callback is called to populate
CustomPlanState node based on the plannode. It also has a table of
callback function pointers to handle various executor's job during quey
execution.

Most of callback designs are not changed from the prior proposition in
v9.4 development cycle, however, here is a few changes.

* CustomPlan became to inherit Scan, and CustomPlanState became to
inherit ScanState. Because some useful routines to implement scan-
logic, like ExecScan, expects state-node has ScanState as its base
type, it's more kindness for extension side. (I'd like to avoid each
extension reinvent ExecScan by copy & paste!)
I'm not sure whether it should be a union of Join in the future, however,
it is a reasonable choice to have compatible layout with Scan/ScanState
to implement alternative "scan" logic.

* Exporting static functions - I still don't have a graceful answer here.
However, it is quite natural that extensions to follow up interface updates
on the future version up of PostgreSQL.
Probably, it shall become clear what class of functions shall be
exported and what class of functions shall be re-implemented within
extension side in the later discussion.
Right now, I exported minimum ones that are needed to implement
alternative scan method - contrib/ctidscan module.

Items to be discussed later:
* planner integration for relations join - probably, we may define new
custom-plan classes as alternative of hash-join, merge-join and
nest-loop. If core can know this custom-plan is alternative of hash-
join, we can utilize core code to check legality of join.
* generic key-value style options in custom-plan definition - Hanada
san proposed me off-list - like foreign data wrapper. It may enable
to configure multiple behavior on a binary.
* ownership and access control of custom-plan. right now, only
superuser can create/drop custom-plan provider definition, thus,
it has no explicit ownership and access control. It seems to me
a reasonable assumption, however, we may have a usecase that
needs custom-plan by unprivileged users.

Thanks,

2014-05-12 10:09 GMT+09:00 Kouhei Kaigai <kaigai@ak.jp.nec.com>:

On 8 May 2014 22:55, Tom Lane <tgl@sss.pgh.pa.us> wrote:

We're past the prototyping stage and into productionising what we
know works, AFAIK. If that point is not clear, then we need to
discuss that first.

OK, I'll bite: what here do we know works? Not a damn thing AFAICS;
it's all speculation that certain hooks might be useful, and
speculation that's not supported by a lot of evidence. If you think
this isn't prototyping, I wonder what you think *is* prototyping.

My research contacts advise me of this recent work
http://www.ntu.edu.sg/home/bshe/hashjoinonapu_vldb13.pdf
and also that they expect a prototype to be ready by October, which I have
been told will be open source.

So there are at least two groups looking at this as a serious option for
Postgres (not including the above paper's authors).

That isn't *now*, but it is at least a time scale that fits with acting
on this in the next release, if we can separate out the various ideas and
agree we wish to proceed.

I'll submerge again...

Through the discussion last week, our minimum consensus are:
1. Deregulated enhancement of FDW is not a way to go
2. Custom-path that can replace built-in scan makes sense as a first step
towards the future enhancement. Its planner integration is enough simple
to do.
3. Custom-path that can replace built-in join takes investigation how to
integrate existing planner structure, to avoid (3a) reinvention of
whole of join handling in extension side, and (3b) unnecessary extension
calls towards the case obviously unsupported.

So, I'd like to start the (2) portion towards the upcoming 1st commit-fest
on the v9.5 development cycle. Also, we will be able to have discussion
for the (3) portion concurrently, probably, towards 2nd commit-fest.

Unfortunately, I cannot participate PGcon/Ottawa this year. Please share
us the face-to-face discussion here.

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

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

--
Shigeru HANADA

--
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: Shigeru Hanada (#2)
1 attachment(s)

Hanada-san,

Thanks for your checks. I oversight the points when I submit the patch, sorry.
The attached one is revised one on documentation stuff and contrib/Makefile.

Thanks,

2014-06-16 17:29 GMT+09:00 Shigeru Hanada <shigeru.hanada@gmail.com>:

Kaigai-san,

I've just applied v1 patch, and tried build and install, but I found two issues:

1) The contrib/ctidscan is not automatically built/installed because
it's not described in contrib/Makefile. Is this expected behavior?
2) I got an error message below when building document.

$ cd doc/src/sgml
$ make
openjade -wall -wno-unused-param -wno-empty -wfully-tagged -D . -D .
-d stylesheet.dsl -t sgml -i output-html -V html-index postgres.sgml
openjade:catalogs.sgml:2525:45:X: reference to non-existent ID
"SQL-CREATECUSTOMPLAN"
make: *** [HTML.index] Error 1
make: *** Deleting file `HTML.index'

I'll review another part of the patch, including the design.

2014-06-14 10:59 GMT+09:00 Kohei KaiGai <kaigai@kaigai.gr.jp>:

According to the discussion upthread, I revised the custom-plan patch
to focus on regular relation scan but no join support right now, and to
support DDL command to define custom-plan providers.

Planner integration with custom logic to scan a particular relation is
enough simple, unlike various join cases. It's almost similar to what
built-in logic are doing now - custom-plan provider adds a path node
with its cost estimation if it can offer alternative way to scan referenced
relation. (in case of no idea, it does not need to add any paths)

A new DDL syntax I'd like to propose is below:

CREATE CUSTOM PLAN <name> FOR <class> PROVIDER <function_name>;

<name> is as literal, put a unique identifier.
<class> is workload type to be offered by this custom-plan provider.
"scan" is the only option right now, that means base relation scan.
<function_name> is also as literal; it shall perform custom-plan provider.

A custom-plan provider function is assumed to take an argument of
"internal" type to deliver a set of planner information that is needed to
construct custom-plan pathnode.
In case of "scan" class, pointer towards an customScanArg object
shall be delivered on invocation of custom-plan provider.

typedef struct {
uint32 custom_class;
PlannerInfo *root;
RelOptInfo *baserel;
RangeTblEntry *rte;
} customScanArg;

In case when the custom-plan provider function being invoked thought
it can offer an alternative scan path on the relation of "baserel", things
to do is (1) construct a CustomPath (or its inherited data type) object
with a table of callback function pointers (2) put its own cost estimation,
and (3) call add_path() to register this path as an alternative one.

Once the custom-path was chosen by query planner, its CreateCustomPlan
callback is called to populate CustomPlan node based on the pathnode.
It also has a table of callback function pointers to handle various planner's
job in setrefs.c and so on.

Similarly, its CreateCustomPlanState callback is called to populate
CustomPlanState node based on the plannode. It also has a table of
callback function pointers to handle various executor's job during quey
execution.

Most of callback designs are not changed from the prior proposition in
v9.4 development cycle, however, here is a few changes.

* CustomPlan became to inherit Scan, and CustomPlanState became to
inherit ScanState. Because some useful routines to implement scan-
logic, like ExecScan, expects state-node has ScanState as its base
type, it's more kindness for extension side. (I'd like to avoid each
extension reinvent ExecScan by copy & paste!)
I'm not sure whether it should be a union of Join in the future, however,
it is a reasonable choice to have compatible layout with Scan/ScanState
to implement alternative "scan" logic.

* Exporting static functions - I still don't have a graceful answer here.
However, it is quite natural that extensions to follow up interface updates
on the future version up of PostgreSQL.
Probably, it shall become clear what class of functions shall be
exported and what class of functions shall be re-implemented within
extension side in the later discussion.
Right now, I exported minimum ones that are needed to implement
alternative scan method - contrib/ctidscan module.

Items to be discussed later:
* planner integration for relations join - probably, we may define new
custom-plan classes as alternative of hash-join, merge-join and
nest-loop. If core can know this custom-plan is alternative of hash-
join, we can utilize core code to check legality of join.
* generic key-value style options in custom-plan definition - Hanada
san proposed me off-list - like foreign data wrapper. It may enable
to configure multiple behavior on a binary.
* ownership and access control of custom-plan. right now, only
superuser can create/drop custom-plan provider definition, thus,
it has no explicit ownership and access control. It seems to me
a reasonable assumption, however, we may have a usecase that
needs custom-plan by unprivileged users.

Thanks,

2014-05-12 10:09 GMT+09:00 Kouhei Kaigai <kaigai@ak.jp.nec.com>:

On 8 May 2014 22:55, Tom Lane <tgl@sss.pgh.pa.us> wrote:

We're past the prototyping stage and into productionising what we
know works, AFAIK. If that point is not clear, then we need to
discuss that first.

OK, I'll bite: what here do we know works? Not a damn thing AFAICS;
it's all speculation that certain hooks might be useful, and
speculation that's not supported by a lot of evidence. If you think
this isn't prototyping, I wonder what you think *is* prototyping.

My research contacts advise me of this recent work
http://www.ntu.edu.sg/home/bshe/hashjoinonapu_vldb13.pdf
and also that they expect a prototype to be ready by October, which I have
been told will be open source.

So there are at least two groups looking at this as a serious option for
Postgres (not including the above paper's authors).

That isn't *now*, but it is at least a time scale that fits with acting
on this in the next release, if we can separate out the various ideas and
agree we wish to proceed.

I'll submerge again...

Through the discussion last week, our minimum consensus are:
1. Deregulated enhancement of FDW is not a way to go
2. Custom-path that can replace built-in scan makes sense as a first step
towards the future enhancement. Its planner integration is enough simple
to do.
3. Custom-path that can replace built-in join takes investigation how to
integrate existing planner structure, to avoid (3a) reinvention of
whole of join handling in extension side, and (3b) unnecessary extension
calls towards the case obviously unsupported.

So, I'd like to start the (2) portion towards the upcoming 1st commit-fest
on the v9.5 development cycle. Also, we will be able to have discussion
for the (3) portion concurrently, probably, towards 2nd commit-fest.

Unfortunately, I cannot participate PGcon/Ottawa this year. Please share
us the face-to-face discussion here.

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

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

--
Shigeru HANADA

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

Attachments:

pgsql-v9.5-custom-plan.v2.patchapplication/octet-stream; name=pgsql-v9.5-custom-plan.v2.patchDownload
 contrib/Makefile                              |   1 +
 contrib/ctidscan/Makefile                     |  19 +
 contrib/ctidscan/ctidscan--1.0.sql            |  12 +
 contrib/ctidscan/ctidscan--unpackaged-1.0.sql |   0
 contrib/ctidscan/ctidscan.c                   | 951 ++++++++++++++++++++++++++
 contrib/ctidscan/ctidscan.control             |   5 +
 contrib/ctidscan/expected/ctidscan.out        | 294 ++++++++
 contrib/ctidscan/sql/ctidscan.sql             |  50 ++
 doc/src/sgml/catalogs.sgml                    |  59 ++
 doc/src/sgml/ref/allfiles.sgml                |   2 +
 doc/src/sgml/ref/create_custom_plan.sgml      | 135 ++++
 doc/src/sgml/ref/drop_custom_plan.sgml        | 108 +++
 doc/src/sgml/reference.sgml                   |   2 +
 src/backend/catalog/Makefile                  |   2 +-
 src/backend/catalog/dependency.c              |  11 +-
 src/backend/catalog/objectaddress.c           |  59 ++
 src/backend/commands/Makefile                 |   2 +-
 src/backend/commands/custom_plan.c            | 186 +++++
 src/backend/commands/dropcmds.c               |   5 +
 src/backend/commands/event_trigger.c          |   2 +
 src/backend/commands/explain.c                |  37 +
 src/backend/executor/Makefile                 |   2 +-
 src/backend/executor/execAmi.c                |  23 +
 src/backend/executor/execProcnode.c           |  19 +
 src/backend/executor/nodeCustom.c             | 135 ++++
 src/backend/nodes/copyfuncs.c                 |  34 +
 src/backend/nodes/equalfuncs.c                |  14 +
 src/backend/nodes/outfuncs.c                  |  33 +
 src/backend/optimizer/path/allpaths.c         |  30 +-
 src/backend/optimizer/plan/createplan.c       |  97 ++-
 src/backend/optimizer/plan/setrefs.c          |  11 +-
 src/backend/optimizer/plan/subselect.c        |  15 +
 src/backend/optimizer/util/pathnode.c         | 110 +++
 src/backend/parser/gram.y                     |  93 ++-
 src/backend/tcop/utility.c                    |  16 +
 src/backend/utils/adt/ruleutils.c             |  54 ++
 src/backend/utils/cache/syscache.c            |  23 +
 src/include/catalog/dependency.h              |   1 +
 src/include/catalog/indexing.h                |   6 +
 src/include/catalog/pg_custom_plan.h          |  49 ++
 src/include/catalog/pg_operator.h             |   3 +
 src/include/commands/defrem.h                 |   5 +
 src/include/executor/nodeCustom.h             |  30 +
 src/include/nodes/execnodes.h                 |  41 ++
 src/include/nodes/nodes.h                     |   5 +
 src/include/nodes/parsenodes.h                |  13 +
 src/include/nodes/plannodes.h                 |  40 ++
 src/include/nodes/relation.h                  |  28 +
 src/include/optimizer/pathnode.h              |  14 +
 src/include/optimizer/planmain.h              |   2 +
 src/include/parser/kwlist.h                   |   4 +
 src/include/utils/syscache.h                  |   2 +
 src/test/regress/expected/sanity_check.out    |   1 +
 53 files changed, 2859 insertions(+), 36 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..1e476a6
--- /dev/null
+++ b/contrib/ctidscan/Makefile
@@ -0,0 +1,19 @@
+# contrib/ctidscan/Makefile
+
+MODULES = ctidscan
+
+EXTENSION = ctidscan
+DATA = ctidscan--1.0.sql
+
+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--1.0.sql b/contrib/ctidscan/ctidscan--1.0.sql
new file mode 100644
index 0000000..3eec965
--- /dev/null
+++ b/contrib/ctidscan/ctidscan--1.0.sql
@@ -0,0 +1,12 @@
+--
+-- Create ctidscan handler function
+--
+CREATE FUNCTION ctidscanaddpath(internal)
+  RETURNS pg_catalog.void
+  AS 'MODULE_PATHNAME','CtidScanAddPath'
+  LANGUAGE C STRICT;
+
+--
+-- Create a custom-plan provider
+--
+CREATE CUSTOM PLAN ctidscan FOR scan PROVIDER ctidscanaddpath;
diff --git a/contrib/ctidscan/ctidscan--unpackaged-1.0.sql b/contrib/ctidscan/ctidscan--unpackaged-1.0.sql
new file mode 100644
index 0000000..e69de29
diff --git a/contrib/ctidscan/ctidscan.c b/contrib/ctidscan/ctidscan.c
new file mode 100644
index 0000000..460b38e
--- /dev/null
+++ b/contrib/ctidscan/ctidscan.c
@@ -0,0 +1,951 @@
+/*
+ * 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_custom_plan.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 {
+	CustomPlan		cplan;
+	List		   *ctid_quals;
+} CtidScanPlan;
+
+typedef struct {
+	CustomPlanState	cps;
+	List		   *ctid_quals;		/* list of ExprState for inequality ops */
+} CtidScanState;
+
+static CustomPathMethods	ctidscan_path_methods;
+static CustomPlanMethods	ctidscan_plan_methods;
+static CustomExecMethods	ctidscan_exec_methods;
+
+/* function declarations */
+void	_PG_init(void);
+Datum	CtidScanAddPath(PG_FUNCTION_ARGS);
+
+#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 undeterministic untill
+		 * executor run it actually.
+		 */
+		num_pages = Max((baserel->pages + 1) / 2, 1);
+	}
+	ntuples *= ((double) num_pages) / ((double) baserel->pages);
+
+	/*
+	 * The TID qual expressions will be computed once, any other baserestrict
+	 * quals once per 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;
+}
+
+/*
+ * CreateCtidScanPlan - A method of CustomPath; that populate a custom
+ * object being delivered from CustomPlan type, according to the supplied
+ * CustomPath object.
+ */
+static CustomPlan *
+CreateCtidScanPlan(PlannerInfo *root, CustomPath *best_path)
+{
+	CtidScanPath   *ctid_path = (CtidScanPath *) best_path;
+	CtidScanPlan   *ctid_plan;
+
+	ctid_plan = palloc0(sizeof(CtidScanPlan));
+	NodeSetTag(ctid_plan, T_CustomPlan);
+	ctid_plan->cplan.methods = &ctidscan_plan_methods;
+	ctid_plan->ctid_quals = ctid_path->ctid_quals;
+
+	return &ctid_plan->cplan;
+}
+
+/*
+ * InitCtidScanPlan
+ *
+ * main portion to initialize CustomPlan node.
+ */
+static Plan *
+InitCtidScanPlan(CustomPlan *custom_plan,
+				 PlannerInfo *root, CustomPath *best_path,
+				 List *tlist, List *clauses)
+{
+	CtidScanPlan   *ctid_scan = (CtidScanPlan *)custom_plan;
+
+	Assert(ctid_scan->cplan.scan.scanrelid > 0);
+
+	/* Set targetlist as is */
+	ctid_scan->cplan.scan.plan.targetlist = tlist;
+	/* Reduce RestrictInfo list to bare expressions */
+	ctid_scan->cplan.scan.plan.qual
+		= extract_actual_clauses(clauses, false);
+
+	/*
+	 * 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 &ctid_scan->cplan.scan.plan;
+}
+
+/*
+ * 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 CustomPlan; 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,
+				   CustomPlan *custom_plan,
+				   int rtoffset)
+{
+	CtidScanPlan   *ctidscan = (CtidScanPlan *) custom_plan;
+	Scan		   *scan = &ctidscan->cplan.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);
+}
+
+/*
+ * SupportCtidBackwardScan - A method of CustomPlan; that informs the core
+ * backend whether this custom-plan node support backward scan or not.
+ */
+static bool
+SupportCtidBackwardScan(CustomPlan *custom_plan)
+{
+	return true;
+}
+
+/*
+ * FinalizeCtidScanPlan - A method of CustomPlan; that handles callbacks
+ * by finalize_plan().
+ */
+static Bitmapset *
+FinalizeCtidScanPlan(PlannerInfo *root,
+					 CustomPlan *custom_plan,
+					 Bitmapset *paramids,
+					 Bitmapset *valid_params,
+					 Bitmapset *scan_params,
+					 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);
+
+	return bms_add_members(paramids, scan_params);
+}
+
+/*
+ * CreateCtidScanState - A method of CustomPlan; that populate a custom
+ * object being delivered from CustomPlanState type, according to the
+ * supplied CustomPath object.
+ */
+static CustomPlanState *
+CreateCtidScanState(CustomPlan *custom_plan)
+{
+	CtidScanState  *ctss = palloc0(sizeof(CtidScanState));
+
+	NodeSetTag(ctss, T_CustomPlanState);
+	ctss->cps.methods = &ctidscan_exec_methods;
+
+	return &ctss->cps;
+}
+
+/*
+ * TextOutCtidScanPlan - A method of CustomPlan; that generates text
+ * representation of the given object.
+ */
+static void
+TextOutCtidScanPlan(StringInfo str, const CustomPlan *node)
+{
+	CtidScanPlan   *ctid_plan = (CtidScanPlan *) node;
+
+	appendStringInfo(str, " :ctid_quals %s",
+					 nodeToString(ctid_plan->ctid_quals));
+}
+
+/*
+ * CopyCtidScanPlan - A method of CustomPlan; that create a copied object.
+ */
+static CustomPlan *
+CopyCtidScanPlan(const CustomPlan *from)
+{
+	CtidScanPlan   *oldnode = (CtidScanPlan *) from;
+	CtidScanPlan   *newnode = palloc0(sizeof(CtidScanPlan));
+
+	NodeSetTag(newnode, T_CustomPlan);
+	newnode->ctid_quals = copyObject(oldnode->ctid_quals);
+
+	return &newnode->cplan;
+}
+
+/*
+ * BeginCtidScan - A method of CustomPlanState; that initializes
+ * the supplied CtidScanState object, at begining of the executor.
+ */
+static void
+BeginCtidScan(CustomPlanState *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->cps.ss.ss_currentScanDesc = NULL;
+}
+
+/*
+ * ReScanCtidScan - A method of CustomPlanState; that rewind the current
+ * seek position.
+ */
+static void
+ReScanCtidScan(CustomPlanState *node)
+{
+	CtidScanState  *ctss = (CtidScanState *)node;
+	HeapScanDesc	scan = ctss->cps.ss.ss_currentScanDesc;
+	EState		   *estate = node->ss.ps.state;
+	ScanDirection	direction = estate->es_direction;
+	Relation		relation = ctss->cps.ss.ss_currentRelation;
+	ExprContext	   *econtext = ctss->cps.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->cps.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->cps.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->cps.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(CustomPlanState *node)
+{
+	CtidScanState  *ctss = (CtidScanState *) node;
+	HeapScanDesc	scan;
+	TupleTableSlot *slot;
+	EState		   *estate = node->ss.ps.state;
+	ScanDirection	direction = estate->es_direction;
+	HeapTuple		tuple;
+
+	if (!ctss->cps.ss.ss_currentScanDesc)
+		ReScanCtidScan(node);
+	scan = ctss->cps.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->cps.ss.ss_ScanTupleSlot;
+	ExecStoreTuple(tuple, slot, scan->rs_cbuf, false);
+
+	return slot;
+}
+
+static bool
+CTidRecheckCustomScan(CustomPlanState *node, TupleTableSlot *slot)
+{
+	return true;
+}
+
+/*
+ * ExecCtidScan - A method of CustomPlanState; that fetches a tuple
+ * from the relation, if exist anymore.
+ */
+static TupleTableSlot *
+ExecCtidScan(CustomPlanState *node)
+{
+	return ExecScan(&node->ss,
+					(ExecScanAccessMtd) CTidAccessCustomScan,
+					(ExecScanRecheckMtd) CTidRecheckCustomScan);
+}
+
+/*
+ * CTidEndCustomScan - A method of CustomPlanState; that closes heap and
+ * scan descriptor, and release other related resources.
+ */
+static void
+EndCtidScan(CustomPlanState *node)
+{
+	CtidScanState  *ctss = (CtidScanState *)node;
+
+	if (ctss->cps.ss.ss_currentScanDesc)
+		heap_endscan(ctss->cps.ss.ss_currentScanDesc);
+}
+
+/*
+ * ExplanCtidScanTargetRel - A method of CustomPlanState; that output
+ * relation's name to be scanned.
+ */
+static void
+ExplanCtidScanTargetRel(CustomPlanState *node, ExplainState *es)
+{
+	CtidScanState  *ctss = (CtidScanState *) node;
+	CtidScanPlan   *ctid_plan = (CtidScanPlan *) ctss->cps.ss.ps.plan;
+	Index			rti = ctid_plan->cplan.scan.scanrelid;
+	RangeTblEntry   *rte;
+	char		   *objectname = NULL;
+	char		   *namespace = NULL;
+	char		   *refname;
+
+	/* logic copied from ExplainTargetRel */
+	rte = rt_fetch(rti, es->rtable);
+	refname = (char *) list_nth(es->rtable_names, rti - 1);
+	if (refname == NULL)
+		refname = rte->eref->aliasname;
+
+	Assert(rte->rtekind == RTE_RELATION);
+	objectname = get_rel_name(rte->relid);
+	if (es->verbose)
+		namespace = get_namespace_name(get_rel_namespace(rte->relid));
+
+	if (es->format == EXPLAIN_FORMAT_TEXT)
+	{
+		appendStringInfoString(es->str, " on");
+		if (namespace != NULL)
+			appendStringInfo(es->str, " %s.%s", quote_identifier(namespace),
+							 quote_identifier(objectname));
+		else if (objectname != NULL)
+			appendStringInfo(es->str, " %s", quote_identifier(objectname));
+		if (objectname == NULL || strcmp(refname, objectname) != 0)
+			appendStringInfo(es->str, " %s", quote_identifier(refname));
+	}
+	else
+	{
+		if (objectname != NULL)
+			ExplainPropertyText("Relation Name", objectname, es);
+		if (namespace != NULL)
+			ExplainPropertyText("Schema", namespace, es);
+		ExplainPropertyText("Alias", refname, es);
+	}
+}
+
+/*
+ * ExplainCtidScan - A method of CustomPlanState; that shows extra info
+ * on EXPLAIN command.
+ */
+static void
+ExplainCtidScan(CustomPlanState *node, List *ancestors, ExplainState *es)
+{
+	CtidScanState  *ctss = (CtidScanState *) node;
+	CtidScanPlan   *ctid_plan = (CtidScanPlan *) ctss->cps.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);
+	}
+}
+
+/*
+ * ExplainCtidPreScanNode - A method of CustomPlanState; that informs
+ * the core backend relation's rtindex to be referenced, prior to the
+ * main EXPLAIN processing.
+ */
+static void
+ExplainCtidPreScanNode(CustomPlanState *node, Bitmapset **rels_used)
+{
+	CtidScanState  *ctss = (CtidScanState *) node;
+	Index			scanrelid = ((Scan *)ctss->cps.ss.ps.plan)->scanrelid;
+
+	*rels_used = bms_add_member(*rels_used, scanrelid);
+}
+
+/*
+ * Entrypoint of this extension
+ */
+Datum
+CtidScanAddPath(PG_FUNCTION_ARGS)
+{
+	customScanArg  *cscan_arg = (customScanArg *)PG_GETARG_POINTER(0);
+	PlannerInfo	   *root;
+	RangeTblEntry  *rte;
+	RelOptInfo	   *baserel;
+	char			relkind;
+	ListCell	   *lc;
+	List		   *ctid_quals = NIL;
+
+	if (cscan_arg->custom_class != CUSTOMPLAN_CLASS_SCAN)
+		PG_RETURN_VOID();
+
+	root = cscan_arg->root;
+	rte = cscan_arg->rte;
+	baserel = cscan_arg->baserel;
+
+	/* all we can support is regular relations */
+	if (rte->rtekind != RTE_RELATION)
+		PG_RETURN_VOID();
+
+	relkind = get_rel_relkind(rte->relid);
+	if (relkind != RELKIND_RELATION &&
+		relkind != RELKIND_MATVIEW &&
+		relkind != RELKIND_TOASTVALUE)
+		PG_RETURN_VOID();
+
+	/* 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_CustomPlan;
+        ctid_path->cpath.path.parent = baserel;
+        ctid_path->cpath.path.param_info
+			= get_baserel_parampathinfo(root, baserel, required_outer);
+		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);
+    }
+	PG_RETURN_VOID();
+}
+PG_FUNCTION_INFO_V1(CtidScanAddPath);
+
+/*
+ * Entrypoint of this extension
+ */
+void
+_PG_init(void)
+{
+	/* setup ctidscan_path_methods */
+	ctidscan_path_methods.CustomName = "ctidscan";
+	ctidscan_path_methods.CreateCustomPlan = CreateCtidScanPlan;
+	ctidscan_path_methods.TextOutCustomPath = TextOutCtidScanPath;
+
+	/* setup ctidscan_plan_methods */
+	ctidscan_plan_methods.CustomName = "ctidscan";
+	ctidscan_plan_methods.InitCustomPlan = InitCtidScanPlan;
+	ctidscan_plan_methods.SetCustomPlanRef = SetCtidScanPlanRef;
+	ctidscan_plan_methods.SupportBackwardScan = SupportCtidBackwardScan;
+	ctidscan_plan_methods.FinalizeCustomPlan = FinalizeCtidScanPlan;
+	ctidscan_plan_methods.CreateCustomPlanState = CreateCtidScanState;
+	ctidscan_plan_methods.TextOutCustomPlan = TextOutCtidScanPlan;
+	ctidscan_plan_methods.CopyCustomPlan = CopyCtidScanPlan;
+
+	/* setup ctidscan_planstate_methods */
+	ctidscan_exec_methods.CustomName = "ctidscan";
+	ctidscan_exec_methods.BeginCustomPlan = BeginCtidScan;
+	ctidscan_exec_methods.ExecCustomPlan = ExecCtidScan;
+	ctidscan_exec_methods.EndCustomPlan = EndCtidScan;
+	ctidscan_exec_methods.ReScanCustomPlan = ReScanCtidScan;
+	ctidscan_exec_methods.MarkPosCustomPlan = NULL;
+	ctidscan_exec_methods.RestrPosCustomPlan = NULL;
+	ctidscan_exec_methods.ExplainCustomPlanTargetRel = ExplanCtidScanTargetRel;
+	ctidscan_exec_methods.ExplainCustomPlan = ExplainCtidScan;
+	ctidscan_exec_methods.ExplainCustomPreScanNode = ExplainCtidPreScanNode;
+	ctidscan_exec_methods.GetSpecialCustomVar = NULL;
+}
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..ae9c628
--- /dev/null
+++ b/contrib/ctidscan/expected/ctidscan.out
@@ -0,0 +1,294 @@
+--
+-- Regression Tests for Custom Plan APIs
+--
+CREATE EXTENSION ctidscan;
+-- 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                              
+----------------------------------------------------------------------
+ Custom (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 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 (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 (ctidscan) on t1
+               Filter: (ctid < '(2,10)'::tid)
+               ctid quals: (ctid < '(2,10)'::tid)
+   ->  Sort
+         Sort Key: t2.ctid
+         ->  Custom (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)
+
+-- 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..9759065
--- /dev/null
+++ b/contrib/ctidscan/sql/ctidscan.sql
@@ -0,0 +1,50 @@
+--
+-- Regression Tests for Custom Plan APIs
+--
+
+CREATE EXTENSION ctidscan;
+
+-- 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;
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE b like '%789%';
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid = '(2,10)'::tid;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+EXPLAIN (costs off) SELECT * FROM t1 JOIN t2 ON t1.ctid = t2.ctid WHERE t1.ctid < '(2,10)'::tid AND t2.ctid > '(1,75)'::tid;
+
+SELECT ctid,* FROM t1 WHERE ctid < '(1,20)'::tid;
+SELECT ctid,* FROM t1 WHERE ctid > '(4,0)'::tid;
+SELECT ctid,* FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+SELECT t1.ctid,* FROM t1 JOIN t2 ON t1.ctid = t2.ctid WHERE t1.ctid < '(2,10)'::tid AND t2.ctid > '(1,75)'::tid;
+
+
+-- Test cleanup
+DROP SCHEMA regtest_custom_scan CASCADE;
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index b4a06e4..4742231 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -109,6 +109,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-custom-plan"><structname>pg_custom_plan</structname></link></entry>
+      <entry>custom plan providers</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-conversion"><structname>pg_conversion</structname></link></entry>
       <entry>encoding conversion information</entry>
      </row>
@@ -2508,6 +2513,60 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-custom-plan">
+  <title><structname>pg_custom_plan</structname></title>
+
+  <indexterm zone="catalog-pg-custom-plan">
+   <primary>pg_custom_plan</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_custom_plan</structname> describes
+   custom-plan providers. See <xref linkend="sql-createcustomplan">
+   for more information.
+  </para>
+
+  <table>
+   <title><structname>pg_custom_plan</> Columns</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>References</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><structfield>oid</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry></entry>
+      <entry>Row identifier (hidden attribute; must be explicitly selected)</entry>
+     </row>
+     <row>
+      <entry><structfield>custname</structfield></entry>
+      <entry><type>name</type></entry>
+      <entry></entry>
+      <entry>custom-plan name</entry>
+     </row>
+     <row>
+      <entry><structfield>custclass</structfield></entry>
+      <entry><type>char</type></entry>
+      <entry></entry>
+      <entry>class of plan node on which custom-plna performs</entry>
+     </row>
+     <row>
+      <entry><structfield>custprovider</structfield></entry>
+      <entry><type>regproc</type></entry>
+      <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
+      <entry>custom-plan provder function</entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
  <sect1 id="catalog-pg-database">
   <title><structname>pg_database</structname></title>
 
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index 1b0962c..f1b87f0 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -55,6 +55,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY createCast         SYSTEM "create_cast.sgml">
 <!ENTITY createCollation    SYSTEM "create_collation.sgml">
 <!ENTITY createConversion   SYSTEM "create_conversion.sgml">
+<!ENTITY createCustomPlan   SYSTEM "create_custom_plan.sgml">
 <!ENTITY createDatabase     SYSTEM "create_database.sgml">
 <!ENTITY createDomain       SYSTEM "create_domain.sgml">
 <!ENTITY createEventTrigger SYSTEM "create_event_trigger.sgml">
@@ -95,6 +96,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY dropCast           SYSTEM "drop_cast.sgml">
 <!ENTITY dropCollation      SYSTEM "drop_collation.sgml">
 <!ENTITY dropConversion     SYSTEM "drop_conversion.sgml">
+<!ENTITY dropCustomPlan     SYSTEM "drop_custom_plan.sgml">
 <!ENTITY dropDatabase       SYSTEM "drop_database.sgml">
 <!ENTITY dropDomain         SYSTEM "drop_domain.sgml">
 <!ENTITY dropEventTrigger   SYSTEM "drop_event_trigger.sgml">
diff --git a/doc/src/sgml/ref/create_custom_plan.sgml b/doc/src/sgml/ref/create_custom_plan.sgml
new file mode 100644
index 0000000..959bf05
--- /dev/null
+++ b/doc/src/sgml/ref/create_custom_plan.sgml
@@ -0,0 +1,135 @@
+<!--
+doc/src/sgml/ref/create_custom_plan.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-CREATECUSTOMPLAN">
+ <indexterm zone="sql-createcustomplan">
+  <primary>CREATE CUSTOM PLAN</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>CREATE CUSTOM PLAN</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>CREATE CUSTOM PLAN</refname>
+  <refpurpose>define a new custom plan provider</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+CREATE CUSTOM PLAN <replaceable class="parameter">provider_name</replaceable> FOR <replaceable class="parameter">custom_class</replaceable>
+    PROVIDER <replaceable class="parameter">provider_function</replaceable>
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>CREATE CUSTOM PLAN</command> defines a new custom-plan provider.
+   The user who defines the custom-plan provider has to be a superuser.
+  </para>
+
+  <para>
+   A custom-plan provider can offer the query planenr alternative options
+   to scan relation, or potentially join relations and so on, in addition
+   to the built-in logics. It is usually extension modules that implements
+   callbacks according to the custom-plan interface.
+  </para>
+  <para>
+   This statement defines a couple of an entrypoint of custom-plan provider
+   and its supporting workload type. Right now, <literal>scan</literal> is
+   the only class being supported; that enables to call extension's
+   callback during query execution instead of built-in routines like
+   <literal>SeqScan</literal> or <literal>IndexScan</literal> if its
+   cost estimation is enough reasonable.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">provider_name</replaceable></term>
+    <listitem>
+     <para>
+      The name of the custom-plan provider to be created.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">custom_class</replaceable></term>
+    <listitem>
+     <para>
+      Workload type on which custom-plan provider can perform.
+      Only <literal>scan</literal> is supported option right now.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">provider_function</replaceable></term>
+    <listitem>
+     <para>
+      A function to be called when query planner is finding the best path
+      to scan a relation.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+   The function that performs as a custom-plan provider shall be declared
+   to return <literal>void</> and take one argument with <literal>internal</>
+   data type.
+   The core <productname>PostgreSQL</> calls custom-plan provider function
+   with a set of information about planner's state and relation(s) to be
+   scanned, then this function shall check whether it can offer alternative
+   scan paths or not.
+   If available, it constructs a path object derived from
+   <literal>CustomPath</> structure, that contains a set of callbacks
+   including the ones to populate <literal>CustomPlan</> or
+   <literal>CustomPlanState</> object later.
+   If extension needs to save its private information in these object,
+   define a new structure that extends above data types.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+  <para>
+   Create a custom-plan provider <literal>ctidscan</> that uses the funcion
+   <literal>ctidscanaddpath</>.   
+<programlisting>
+CREATE CUSTOM PLAN ctidscan FOR scan PROVIDER ctidscanaddpath;
+</programlisting>
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+  <para>
+   There is no <command>CREATE CUSTOM PLAN</command> command
+   in the SQL standard.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-dropcustomplan"></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/drop_custom_plan.sgml b/doc/src/sgml/ref/drop_custom_plan.sgml
new file mode 100644
index 0000000..b72146b
--- /dev/null
+++ b/doc/src/sgml/ref/drop_custom_plan.sgml
@@ -0,0 +1,108 @@
+<!--
+doc/src/sgml/ref/drop_custom_plan.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-DROPCUSTOMPLAN">
+ <indexterm zone="sql-dropcustomplan">
+  <primary>DROP CUSTOM PLAN</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>DROP CUSTOM PLAN</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>DROP CUSTOM PLAN</refname>
+  <refpurpose>remove a custom-plan provider</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+DROP CUSTOM PLAN [ IF EXISTS ] <replaceable class="parameter">name</replaceable> [ CASCADE | RESTRICT ]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>DROP CUSTOM PLAN</command> removes an existing custom-plan
+   provider. To execute this command, the current user must be superuser.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>IF EXISTS</literal></term>
+    <listitem>
+     <para>
+      Do not throw an error if the custom-plan provider does not exist.
+      A notice is issued in this case.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name of an existing custom-plan provider.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>CASCADE</literal></term>
+    <listitem>
+     <para>
+      Automatically drop objects that depend on the custom-plan provider.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>RESTRICT</literal></term>
+    <listitem>
+     <para>
+      Refuse to drop the custom-plan provider if any objects depend on it.
+      This is the default.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   Drop a custom-plan provider <literal>foo</> if it exists:
+<programlisting>
+DROP CUSTOM PLAN IF EXISTS foo;
+</programlisting></para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   There is no <command>DROP CUSTOM PLAN</command> command
+   in the SQL standard.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-createcustomplan"></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index a6575f5..372dc04 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -83,6 +83,7 @@
    &createCast;
    &createCollation;
    &createConversion;
+   &createCustomPlan;
    &createDatabase;
    &createDomain;
    &createEventTrigger;
@@ -123,6 +124,7 @@
    &dropCast;
    &dropCollation;
    &dropConversion;
+   &dropCustomPlan;
    &dropDatabase;
    &dropDomain;
    &dropEventTrigger;
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index a974bd5..83b02e9 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -39,7 +39,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
 	pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
 	pg_ts_parser.h pg_ts_template.h pg_extension.h \
 	pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
-	pg_foreign_table.h \
+	pg_foreign_table.h pg_custom_plan.h \
 	pg_default_acl.h pg_seclabel.h pg_shseclabel.h pg_collation.h pg_range.h \
 	toasting.h indexing.h \
     )
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index d41ba49..ec01170 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -30,6 +30,7 @@
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_conversion.h"
 #include "catalog/pg_conversion_fn.h"
+#include "catalog/pg_custom_plan.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_default_acl.h"
 #include "catalog/pg_depend.h"
@@ -154,7 +155,8 @@ static const Oid object_classes[MAX_OCLASS] = {
 	UserMappingRelationId,		/* OCLASS_USER_MAPPING */
 	DefaultAclRelationId,		/* OCLASS_DEFACL */
 	ExtensionRelationId,		/* OCLASS_EXTENSION */
-	EventTriggerRelationId		/* OCLASS_EVENT_TRIGGER */
+	EventTriggerRelationId,		/* OCLASS_EVENT_TRIGGER */
+	CustomPlanRelationId,		/* OCLASS_CUSTOM_PLAN */
 };
 
 
@@ -1249,6 +1251,10 @@ doDeletion(const ObjectAddress *object, int flags)
 			RemoveEventTriggerById(object->objectId);
 			break;
 
+		case OCLASS_CUSTOM_PLAN:
+			RemoveCustomPlanById(object->objectId);
+			break;
+
 		default:
 			elog(ERROR, "unrecognized object class: %u",
 				 object->classId);
@@ -2316,6 +2322,9 @@ getObjectClass(const ObjectAddress *object)
 
 		case EventTriggerRelationId:
 			return OCLASS_EVENT_TRIGGER;
+
+		case CustomPlanRelationId:
+			return OCLASS_CUSTOM_PLAN;
 	}
 
 	/* shouldn't get here */
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index c7c8f4b..2a5b5cf 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -30,6 +30,7 @@
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_conversion.h"
+#include "catalog/pg_custom_plan.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_extension.h"
 #include "catalog/pg_foreign_data_wrapper.h"
@@ -152,6 +153,18 @@ static const ObjectPropertyType ObjectProperty[] =
 		true
 	},
 	{
+		CustomPlanRelationId,
+		CustomPlanOidIndexId,
+		CUSTOMPLANOID,
+		CUSTOMPLANNAME,
+		Anum_pg_custom_plan_custname,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		-1,
+		false,
+	},
+	{
 		DatabaseRelationId,
 		DatabaseOidIndexId,
 		DATABASEOID,
@@ -529,6 +542,7 @@ get_object_address(ObjectType objtype, List *objname, List *objargs,
 			case OBJECT_FDW:
 			case OBJECT_FOREIGN_SERVER:
 			case OBJECT_EVENT_TRIGGER:
+			case OBJECT_CUSTOM_PLAN:
 				address = get_object_address_unqualified(objtype,
 														 objname, missing_ok);
 				break;
@@ -755,6 +769,9 @@ get_object_address_unqualified(ObjectType objtype,
 			case OBJECT_EVENT_TRIGGER:
 				msg = gettext_noop("event trigger name cannot be qualified");
 				break;
+			case OBJECT_CUSTOM_PLAN:
+				msg = gettext_noop("custom plan name cannot be qualified");
+				break;
 			default:
 				elog(ERROR, "unrecognized objtype: %d", (int) objtype);
 				msg = NULL;		/* placate compiler */
@@ -815,6 +832,11 @@ get_object_address_unqualified(ObjectType objtype,
 			address.objectId = get_event_trigger_oid(name, missing_ok);
 			address.objectSubId = 0;
 			break;
+		case OBJECT_CUSTOM_PLAN:
+			address.classId = CustomPlanRelationId;
+			address.objectId = get_custom_plan_oid(name, missing_ok);
+			address.objectSubId = 0;
+			break;
 		default:
 			elog(ERROR, "unrecognized objtype: %d", (int) objtype);
 			/* placate compiler, which doesn't know elog won't return */
@@ -1295,6 +1317,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
 			break;
 		case OBJECT_TSPARSER:
 		case OBJECT_TSTEMPLATE:
+		case OBJECT_CUSTOM_PLAN:
 			/* We treat these object types as being owned by superusers */
 			if (!superuser_arg(roleid))
 				ereport(ERROR,
@@ -2166,6 +2189,21 @@ getObjectDescription(const ObjectAddress *object)
 				break;
 			}
 
+		case OCLASS_CUSTOM_PLAN:
+			{
+				HeapTuple	tup;
+
+				tup = SearchSysCache1(CUSTOMPLANOID,
+									  ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "cache lookup failed for custom-plan %u",
+						 object->objectId);
+				appendStringInfo(&buffer, _("custom plan %s"),
+					NameStr(((Form_pg_custom_plan) GETSTRUCT(tup))->custname));
+				ReleaseSysCache(tup);
+				break;
+			}
+
 		default:
 			appendStringInfo(&buffer, "unrecognized object %u %u %d",
 							 object->classId,
@@ -2577,6 +2615,10 @@ getObjectTypeDescription(const ObjectAddress *object)
 			appendStringInfoString(&buffer, "event trigger");
 			break;
 
+		case OCLASS_CUSTOM_PLAN:
+			appendStringInfoString(&buffer, "custom plan");
+			break;
+
 		default:
 			appendStringInfo(&buffer, "unrecognized %u", object->classId);
 			break;
@@ -3330,6 +3372,23 @@ getObjectIdentity(const ObjectAddress *object)
 				break;
 			}
 
+		case OCLASS_CUSTOM_PLAN:
+			{
+				HeapTuple	tup;
+				Form_pg_custom_plan custForm;
+
+				tup = SearchSysCache1(CUSTOMPLANOID,
+									  ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "cache lookup failed for custom-plan %u",
+						 object->objectId);
+				custForm = (Form_pg_custom_plan) GETSTRUCT(tup);
+				appendStringInfoString(&buffer,
+						quote_identifier(NameStr(custForm->custname)));
+				ReleaseSysCache(tup);
+				break;
+			}
+
 		default:
 			appendStringInfo(&buffer, "unrecognized object %u %u %d",
 							 object->classId,
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index 22f116b..1e8e6f4 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -14,7 +14,7 @@ include $(top_builddir)/src/Makefile.global
 
 OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o  \
 	collationcmds.o constraint.o conversioncmds.o copy.o createas.o \
-	dbcommands.o define.o discard.o dropcmds.o \
+	custom_plan.o dbcommands.o define.o discard.o dropcmds.o \
 	event_trigger.o explain.o extension.o foreigncmds.o functioncmds.o \
 	indexcmds.o lockcmds.o matview.o operatorcmds.o opclasscmds.o \
 	portalcmds.o prepare.o proclang.o \
diff --git a/src/backend/commands/custom_plan.c b/src/backend/commands/custom_plan.c
new file mode 100644
index 0000000..27cd75d
--- /dev/null
+++ b/src/backend/commands/custom_plan.c
@@ -0,0 +1,186 @@
+/*-------------------------------------------------------------------------
+ *
+ * custom_plan.c
+ *		custom plan nodes creation/manipulation commands
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ *    src/backend/commands/custom_plan.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_custom_plan.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+#include "commands/defrem.h"
+#include "fmgr.h"
+#include "miscadmin.h"
+#include "parser/parse_func.h"
+#include "utils/builtins.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+/*
+ * utility function to lookup a custom-plan provider by name
+ */
+Oid
+get_custom_plan_oid(const char *custom_name, bool missing_ok)
+{
+	Oid		cust_oid;
+
+	cust_oid = GetSysCacheOid1(CUSTOMPLANNAME, CStringGetDatum(custom_name));
+	if (!OidIsValid(cust_oid) && !missing_ok)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("custom-plan provider \"%s\" does not exist",
+						custom_name)));
+	return cust_oid;
+}
+
+/*
+ * Drop a custom-plan provider
+ */
+void
+RemoveCustomPlanById(Oid cust_oid)
+{
+	Relation	rel;
+	HeapTuple	tuple;
+
+	rel = heap_open(CustomPlanRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCache1(CUSTOMPLANOID, ObjectIdGetDatum(cust_oid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for custom-plan provider %u",
+			 cust_oid);
+
+	simple_heap_delete(rel, &tuple->t_self);
+
+	ReleaseSysCache(tuple);
+
+	heap_close(rel, RowExclusiveLock);
+}
+
+/*
+ * Create a custom-plan provider
+ */
+Oid
+DefineCustomPlan(CreateCustomPlanStmt *stmt)
+{
+	Relation	rel;
+	Oid			cust_oid;
+	Oid			cust_provider = InvalidOid;
+	Datum		values[Natts_pg_custom_plan];
+	bool		isnull[Natts_pg_custom_plan];
+	HeapTuple	tuple;
+	ListCell   *cell;
+	ObjectAddress myself;
+	ObjectAddress referenced;
+
+	rel = heap_open(CustomPlanRelationId, RowExclusiveLock);
+
+	/* must be super user */
+	if (!superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+			 errmsg("permission denied to create custom-plan provider \"%s\"",
+					stmt->custom_name),
+			 errhint("Must be superuser to create a custom-plan node.")));
+
+	/* check namespace conflicts */
+	cust_oid = get_custom_plan_oid(stmt->custom_name, true);
+	if (OidIsValid(cust_oid))
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("custom-plan provider \"%s\" already exists",
+						stmt->custom_name)));
+
+	/* check custom-plan class */
+	if (stmt->custom_class != CUSTOMPLAN_CLASS_SCAN)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("unexpected custom plan class specified: %d",
+						(int)stmt->custom_class)));
+
+	/* parse custom-plan options */
+	foreach (cell, stmt->custom_options)
+	{
+		DefElem	   *defel = lfirst(cell);
+
+		Assert(IsA(defel, DefElem));
+
+		if (strcmp(defel->defname, "provider") == 0)
+		{
+			Oid		argtypes[1];
+
+			if (OidIsValid(cust_provider))
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+
+			argtypes[0] = INTERNALOID;
+			cust_provider = LookupFuncName((List *)defel->arg,
+										  1, argtypes, false);
+			if (get_func_rettype(cust_provider) != VOIDOID)
+				ereport(ERROR,
+						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+						 errmsg("function %s must return type \"void\"",
+								NameListToString((List *) defel->arg))));
+		}
+		else
+			elog(ERROR, "unexpected custom-plan option: %s",
+				 defel->defname);
+	}
+
+	if (!OidIsValid(cust_provider))
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("PROVIDER must be provided")));
+
+	/*
+	 * Insert tuple into pg_custom_plan system catalog
+	 */
+	memset(values, 0, sizeof(values));
+	memset(isnull, 0, sizeof(isnull));
+	values[Anum_pg_custom_plan_custname - 1]
+		= DirectFunctionCall1(namein, CStringGetDatum(stmt->custom_name));
+	values[Anum_pg_custom_plan_custclass - 1]
+		= stmt->custom_class;
+	values[Anum_pg_custom_plan_custprovider - 1]
+		= ObjectIdGetDatum(cust_provider);
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), values, isnull);
+
+	cust_oid = simple_heap_insert(rel, tuple);
+	CatalogUpdateIndexes(rel, tuple);
+
+	heap_freetuple(tuple);
+
+	/* record dependencies */
+	myself.classId = CustomPlanRelationId;
+	myself.objectId = cust_oid;
+	myself.objectSubId = 0;
+
+	referenced.classId = ProcedureRelationId;
+	referenced.objectId = cust_provider;
+	referenced.objectSubId = 0;
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+	/* Post creation hook for new custom-plan provider */
+	InvokeObjectPostCreateHook(CustomPlanRelationId, cust_oid, 0);
+
+	heap_close(rel, RowExclusiveLock);
+
+	return cust_oid;
+}
diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c
index e64ad80..104ff17 100644
--- a/src/backend/commands/dropcmds.c
+++ b/src/backend/commands/dropcmds.c
@@ -408,6 +408,11 @@ does_not_exist_skipping(ObjectType objtype, List *objname, List *objargs)
 				args = strVal(linitial(objargs));
 			}
 			break;
+		case OBJECT_CUSTOM_PLAN:
+			msg = gettext_noop("custom-plan \"%s\" does not exist, skipping");
+			name = NameListToString(objname);
+			break;
+
 		default:
 			elog(ERROR, "unexpected object type (%d)", (int) objtype);
 			break;
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 6d4e091..a3468f3 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -923,6 +923,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
 		case OBJECT_CONSTRAINT:
 		case OBJECT_COLLATION:
 		case OBJECT_CONVERSION:
+		case OBJECT_CUSTOM_PLAN:
 		case OBJECT_DOMAIN:
 		case OBJECT_EXTENSION:
 		case OBJECT_FDW:
@@ -975,6 +976,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
 		case OCLASS_COLLATION:
 		case OCLASS_CONSTRAINT:
 		case OCLASS_CONVERSION:
+		case OCLASS_CUSTOM_PLAN:
 		case OCLASS_DEFAULT:
 		case OCLASS_LANGUAGE:
 		case OCLASS_LARGEOBJECT:
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 0d9663c..450a123 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -722,6 +722,14 @@ ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
 			*rels_used = bms_add_member(*rels_used,
 										((Scan *) plan)->scanrelid);
 			break;
+		case T_CustomPlan:
+		case T_CustomPlanMarkPos:
+			{
+				CustomPlanState *cps = (CustomPlanState *)planstate;
+
+				cps->methods->ExplainCustomPreScanNode(cps, rels_used);
+			}
+			break;
 		case T_ModifyTable:
 			/* cf ExplainModifyTarget */
 			*rels_used = bms_add_member(*rels_used,
@@ -848,6 +856,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 +945,14 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_ForeignScan:
 			pname = sname = "Foreign Scan";
 			break;
+		case T_CustomPlan:
+			sname = "Custom";
+			custom_name = ((CustomPlan *) plan)->methods->CustomName;
+			if (custom_name)
+				pname = psprintf("Custom (%s)", custom_name);
+			else
+				pname = sname;
+			break;
 		case T_Material:
 			pname = sname = "Materialize";
 			break;
@@ -1037,6 +1054,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", custom_name, es);
 	}
 
 	switch (nodeTag(plan))
@@ -1084,6 +1103,13 @@ ExplainNode(PlanState *planstate, List *ancestors,
 					ExplainPropertyText("Index Name", indexname, es);
 			}
 			break;
+		case T_CustomPlan:
+			{
+				CustomPlanState *cps = (CustomPlanState *)planstate;
+
+				cps->methods->ExplainCustomPlanTargetRel(cps, es);
+			}
+			break;
 		case T_ModifyTable:
 			ExplainModifyTarget((ModifyTable *) plan, es);
 			break;
@@ -1353,6 +1379,17 @@ ExplainNode(PlanState *planstate, List *ancestors,
 										   planstate, es);
 			show_foreignscan_info((ForeignScanState *) planstate, es);
 			break;
+		case T_CustomPlan:
+			{
+				CustomPlanState *cps = (CustomPlanState *) planstate;
+
+				show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
+				if (plan->qual)
+					show_instrumentation_count("Rows Removed by Filter", 1,
+											   planstate, es);
+				cps->methods->ExplainCustomPlan(cps, 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..5fc9cbb 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -21,6 +21,7 @@
 #include "executor/nodeBitmapIndexscan.h"
 #include "executor/nodeBitmapOr.h"
 #include "executor/nodeCtescan.h"
+#include "executor/nodeCustom.h"
 #include "executor/nodeForeignscan.h"
 #include "executor/nodeFunctionscan.h"
 #include "executor/nodeGroup.h"
@@ -197,6 +198,10 @@ ExecReScan(PlanState *node)
 			ExecReScanForeignScan((ForeignScanState *) node);
 			break;
 
+		case T_CustomPlanState:
+			ExecReScanCustomPlan((CustomPlanState *) node);
+			break;
+
 		case T_NestLoopState:
 			ExecReScanNestLoop((NestLoopState *) node);
 			break;
@@ -291,6 +296,10 @@ ExecMarkPos(PlanState *node)
 			ExecValuesMarkPos((ValuesScanState *) node);
 			break;
 
+		case T_CustomPlanState:
+			ExecCustomMarkPos((CustomPlanState *) node);
+			break;
+
 		case T_MaterialState:
 			ExecMaterialMarkPos((MaterialState *) node);
 			break;
@@ -348,6 +357,10 @@ ExecRestrPos(PlanState *node)
 			ExecValuesRestrPos((ValuesScanState *) node);
 			break;
 
+		case T_CustomPlanState:
+			ExecCustomRestrPos((CustomPlanState *) node);
+			break;
+
 		case T_MaterialState:
 			ExecMaterialRestrPos((MaterialState *) node);
 			break;
@@ -390,6 +403,7 @@ ExecSupportsMarkRestore(NodeTag plantype)
 		case T_ValuesScan:
 		case T_Material:
 		case T_Sort:
+		case T_CustomPlanMarkPos:
 			return true;
 
 		case T_Result:
@@ -465,6 +479,15 @@ ExecSupportsBackwardScan(Plan *node)
 			return ExecSupportsBackwardScan(((SubqueryScan *) node)->subplan) &&
 				TargetListSupportsBackwardScan(node->targetlist);
 
+		case T_CustomPlan:
+			{
+				CustomPlan *cplan = (CustomPlan *) node;
+
+				if (cplan->methods->SupportBackwardScan)
+					return cplan->methods->SupportBackwardScan(cplan);
+			}
+			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..62ebab9 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,12 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
 													   estate, eflags);
 			break;
 
+		case T_CustomPlan:
+		case T_CustomPlanMarkPos:
+			result = (PlanState *) ExecInitCustomPlan((CustomPlan *) node,
+													  estate, eflags);
+			break;
+
 			/*
 			 * join nodes
 			 */
@@ -442,6 +449,10 @@ ExecProcNode(PlanState *node)
 			result = ExecForeignScan((ForeignScanState *) node);
 			break;
 
+		case T_CustomPlanState:
+			result = ExecCustomPlan((CustomPlanState *) node);
+			break;
+
 			/*
 			 * join nodes
 			 */
@@ -558,6 +569,10 @@ MultiExecProcNode(PlanState *node)
 			result = MultiExecBitmapOr((BitmapOrState *) node);
 			break;
 
+		case T_CustomPlanState:
+			result = MultiExecCustomPlan((CustomPlanState *) node);
+			break;
+
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
 			result = NULL;
@@ -678,6 +693,10 @@ ExecEndNode(PlanState *node)
 			ExecEndForeignScan((ForeignScanState *) node);
 			break;
 
+		case T_CustomPlanState:
+			ExecEndCustomPlan((CustomPlanState *) node);
+			break;
+
 			/*
 			 * join nodes
 			 */
diff --git a/src/backend/executor/nodeCustom.c b/src/backend/executor/nodeCustom.c
new file mode 100644
index 0000000..55502fd
--- /dev/null
+++ b/src/backend/executor/nodeCustom.c
@@ -0,0 +1,135 @@
+/* ------------------------------------------------------------------------
+ *
+ * nodeCustom.c
+ *		Routines to handle execution of custom plan 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"
+
+CustomPlanState *
+ExecInitCustomPlan(CustomPlan *cplan, EState *estate, int eflags)
+{
+	CustomPlanState    *cps = cplan->methods->CreateCustomPlanState(cplan);
+
+	/* fill up fields of PlanState */
+	cps->ss.ps.plan = &cplan->scan.plan;
+	cps->ss.ps.state = estate;
+
+	/* create expression context for node */
+	ExecAssignExprContext(estate, &cps->ss.ps);
+	cps->ss.ps.ps_TupFromTlist = false;
+
+	/* initialize child expressions */
+	cps->ss.ps.targetlist = (List *)
+		ExecInitExpr((Expr *) cplan->scan.plan.targetlist,
+					 (PlanState *) cps);
+	cps->ss.ps.qual = (List *)
+		ExecInitExpr((Expr *) cplan->scan.plan.qual,
+					 (PlanState *) cps);
+
+	/* initialization of result tuple slot  */
+	ExecInitResultTupleSlot(estate, &cps->ss.ps);
+	ExecAssignResultTypeFromTL(&cps->ss.ps);
+
+	if (cplan->scan.scanrelid > 0)
+	{
+		Relation	heap_rel;
+
+		heap_rel = ExecOpenScanRelation(estate, cplan->scan.scanrelid, eflags);
+		cps->ss.ss_currentRelation = heap_rel;
+		cps->ss.ss_currentScanDesc = NULL;	/* set by provider on demand */
+		ExecInitScanTupleSlot(estate, &cps->ss);
+		ExecAssignScanType(&cps->ss, RelationGetDescr(heap_rel));
+		ExecAssignScanProjectionInfo(&cps->ss);
+	}
+	else
+	{
+		/*
+		 * Elsewhere, custom-plan provider should be responsible to put
+		 * appropriate initialization of scan tuple-slot and projection
+		 * info.
+		 */
+		cps->ss.ss_currentRelation = NULL;
+		cps->ss.ss_currentScanDesc = NULL;
+		cps->ss.ss_ScanTupleSlot = NULL;
+		cps->ss.ps.ps_ProjInfo = NULL;
+	}
+	/*
+	 * Then, custom-plan provider can have all the own original
+	 * initialization on demand.
+	 */
+	cps->methods->BeginCustomPlan(cps, estate, eflags);
+
+	return cps;
+}
+
+TupleTableSlot *
+ExecCustomPlan(CustomPlanState *cpstate)
+{
+	Assert(cpstate->methods->ExecCustomPlan != NULL);
+	return cpstate->methods->ExecCustomPlan(cpstate);
+}
+
+Node *
+MultiExecCustomPlan(CustomPlanState *cpstate)
+{
+	if (!cpstate->methods->MultiExecCustomPlan)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("CustomPlan \"%s\" does not support MultiExec method",
+						cpstate->methods->CustomName)));
+	return cpstate->methods->MultiExecCustomPlan(cpstate);
+}
+
+void
+ExecEndCustomPlan(CustomPlanState *cpstate)
+{
+	Assert(cpstate->methods->EndCustomPlan != NULL);
+	cpstate->methods->EndCustomPlan(cpstate);
+
+	/* Free the exprcontext */
+	ExecFreeExprContext(&cpstate->ss.ps);
+
+	/* Clean out the tuple table */
+	ExecClearTuple(cpstate->ss.ps.ps_ResultTupleSlot);
+	if (cpstate->ss.ss_ScanTupleSlot)
+		ExecClearTuple(cpstate->ss.ss_ScanTupleSlot);
+
+	/* Close the heap relation, if needed */
+	if (cpstate->ss.ss_currentRelation)
+		ExecCloseScanRelation(cpstate->ss.ss_currentRelation);
+}
+
+void
+ExecReScanCustomPlan(CustomPlanState *cpstate)
+{
+	Assert(cpstate->methods->ReScanCustomPlan != NULL);
+	cpstate->methods->ReScanCustomPlan(cpstate);
+}
+
+void
+ExecCustomMarkPos(CustomPlanState *cpstate)
+{
+	Assert(cpstate->methods->MarkPosCustomPlan != NULL);
+	cpstate->methods->MarkPosCustomPlan(cpstate);
+}
+
+void
+ExecCustomRestrPos(CustomPlanState *cpstate)
+{
+	Assert(cpstate->methods->RestrPosCustomPlan != NULL);
+	cpstate->methods->RestrPosCustomPlan(cpstate);
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 43530aa..a10599a 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -598,6 +598,21 @@ _copyForeignScan(const ForeignScan *from)
 }
 
 /*
+ * _copyCustomPlan
+ */
+static CustomPlan *
+_copyCustomPlan(const CustomPlan *from)
+{
+	CustomPlan *newnode;
+
+	newnode = from->methods->CopyCustomPlan(from);
+	Assert(nodeTag(newnode) == nodeTag(from));
+	CopyScanFields((const Scan *) from, (Scan *) newnode);
+
+	return newnode;
+}
+
+/*
  * CopyJoinFields
  *
  *		This function copies the fields of the Join node.  It is used by
@@ -3821,6 +3836,18 @@ _copyAlterTSConfigurationStmt(const AlterTSConfigurationStmt *from)
 	return newnode;
 }
 
+static CreateCustomPlanStmt *
+_copyCreateCustomPlanStmt(const CreateCustomPlanStmt *from)
+{
+	CreateCustomPlanStmt *newnode = makeNode(CreateCustomPlanStmt);
+
+	COPY_STRING_FIELD(custom_name);
+	COPY_SCALAR_FIELD(custom_class);
+	COPY_NODE_FIELD(custom_options);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					pg_list.h copy functions
  * ****************************************************************
@@ -3984,6 +4011,10 @@ copyObject(const void *from)
 		case T_ForeignScan:
 			retval = _copyForeignScan(from);
 			break;
+		case T_CustomPlan:
+		case T_CustomPlanMarkPos:
+			retval = _copyCustomPlan(from);
+			break;
 		case T_Join:
 			retval = _copyJoin(from);
 			break;
@@ -4530,6 +4561,9 @@ copyObject(const void *from)
 		case T_AlterTSConfigurationStmt:
 			retval = _copyAlterTSConfigurationStmt(from);
 			break;
+		case T_CreateCustomPlanStmt:
+			retval = _copyCreateCustomPlanStmt(from);
+			break;
 
 		case T_A_Expr:
 			retval = _copyAExpr(from);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 2407cb7..fc0165e 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1994,6 +1994,17 @@ _equalAlterTSConfigurationStmt(const AlterTSConfigurationStmt *a,
 }
 
 static bool
+_equalCreateCustomPlanStmt(const CreateCustomPlanStmt *a,
+						   const CreateCustomPlanStmt *b)
+{
+	COMPARE_STRING_FIELD(custom_name);
+	COMPARE_SCALAR_FIELD(custom_class);
+	COMPARE_NODE_FIELD(custom_options);
+
+	return true;
+}
+
+static bool
 _equalAExpr(const A_Expr *a, const A_Expr *b)
 {
 	COMPARE_SCALAR_FIELD(kind);
@@ -2998,6 +3009,9 @@ equal(const void *a, const void *b)
 		case T_AlterTSConfigurationStmt:
 			retval = _equalAlterTSConfigurationStmt(a, b);
 			break;
+		case T_CreateCustomPlanStmt:
+			retval = _equalCreateCustomPlanStmt(a, b);
+			break;
 
 		case T_A_Expr:
 			retval = _equalAExpr(a, b);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index deff33f..16e08de 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -564,6 +564,22 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
 }
 
 static void
+_outCustomPlan(StringInfo str, const CustomPlan *node)
+{
+	if (IsA(node, CustomPlan))
+		WRITE_NODE_TYPE("CUSTOMPLAN");
+	else if (IsA(node, CustomPlanMarkPos))
+		WRITE_NODE_TYPE("CUSTOMPLANMARKPOS");
+	else
+		elog(ERROR, "unexpected node tag given: %d", (int)nodeTag(node));
+
+	_outScanInfo(str, (const Scan *) node);
+	appendStringInfo(str, " :methods");
+	_outToken(str, node->methods->CustomName);
+	node->methods->TextOutCustomPlan(str, node);
+}
+
+static void
 _outJoin(StringInfo str, const Join *node)
 {
 	WRITE_NODE_TYPE("JOIN");
@@ -1581,6 +1597,16 @@ _outForeignPath(StringInfo str, const ForeignPath *node)
 }
 
 static void
+_outCustomPath(StringInfo str, const CustomPath *node)
+{
+	WRITE_NODE_TYPE("CUSTOMPATH");
+	_outPathInfo(str, (const Path *) node);
+	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");
@@ -2820,6 +2846,10 @@ _outNode(StringInfo str, const void *obj)
 			case T_ForeignScan:
 				_outForeignScan(str, obj);
 				break;
+			case T_CustomPlan:
+			case T_CustomPlanMarkPos:
+				_outCustomPlan(str, obj);
+				break;
 			case T_Join:
 				_outJoin(str, obj);
 				break;
@@ -3028,6 +3058,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 fdaa964..bf95408 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -326,7 +326,7 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 				}
 				break;
 			case RTE_SUBQUERY:
-				/* Subquery --- fully handled during set_rel_size */
+				/* Subquery --- path was added during set_rel_size */
 				break;
 			case RTE_FUNCTION:
 				/* RangeFunction */
@@ -337,12 +337,17 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 				set_values_pathlist(root, rel, rte);
 				break;
 			case RTE_CTE:
-				/* CTE reference --- fully handled during set_rel_size */
+				/* CTE reference --- path was added during set_rel_size */
 				break;
 			default:
 				elog(ERROR, "unexpected rtekind: %d", (int) rel->rtekind);
 				break;
 		}
+		/* Also, consider paths by custom-plan providers */
+		call_custom_scan_providers(root, rel, rte);
+
+		/* Select cheapest path */
+		set_cheapest(rel);
 	}
 
 #ifdef OPTIMIZER_DEBUG
@@ -391,9 +396,6 @@ set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 
 	/* Consider TID scans */
 	create_tidscan_paths(root, rel);
-
-	/* Now find the cheapest of the paths for this rel */
-	set_cheapest(rel);
 }
 
 /*
@@ -419,9 +421,6 @@ set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 {
 	/* Call the FDW's GetForeignPaths function to generate path(s) */
 	rel->fdwroutine->GetForeignPaths(root, rel, rte->relid);
-
-	/* Select cheapest path */
-	set_cheapest(rel);
 }
 
 /*
@@ -1256,9 +1255,6 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
 
 	/* Generate appropriate path */
 	add_path(rel, create_subqueryscan_path(root, rel, pathkeys, required_outer));
-
-	/* Select cheapest path (pretty easy in this case...) */
-	set_cheapest(rel);
 }
 
 /*
@@ -1327,9 +1323,6 @@ set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 	/* Generate appropriate path */
 	add_path(rel, create_functionscan_path(root, rel,
 										   pathkeys, required_outer));
-
-	/* Select cheapest path (pretty easy in this case...) */
-	set_cheapest(rel);
 }
 
 /*
@@ -1350,9 +1343,6 @@ set_values_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 
 	/* Generate appropriate path */
 	add_path(rel, create_valuesscan_path(root, rel, required_outer));
-
-	/* Select cheapest path (pretty easy in this case...) */
-	set_cheapest(rel);
 }
 
 /*
@@ -1419,9 +1409,6 @@ set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 
 	/* Generate appropriate path */
 	add_path(rel, create_ctescan_path(root, rel, required_outer));
-
-	/* Select cheapest path (pretty easy in this case...) */
-	set_cheapest(rel);
 }
 
 /*
@@ -1472,9 +1459,6 @@ set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 
 	/* Generate appropriate path */
 	add_path(rel, create_worktablescan_path(root, rel, required_outer));
-
-	/* Select cheapest path (pretty easy in this case...) */
-	set_cheapest(rel);
 }
 
 /*
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 4b641a2..cb07af4 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -77,13 +77,13 @@ 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 Plan *create_custom_plan(PlannerInfo *root, CustomPath *best_path);
 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,
 					  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);
@@ -261,6 +261,9 @@ create_plan_recurse(PlannerInfo *root, Path *best_path)
 			plan = create_unique_plan(root,
 									  (UniquePath *) best_path);
 			break;
+		case T_CustomPlan:
+			plan = create_custom_plan(root, (CustomPath *) best_path);
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) best_path->pathtype);
@@ -1072,6 +1075,96 @@ 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 'scan_clauses' and targetlist 'tlist'.
+ */
+static Plan *
+create_custom_plan(PlannerInfo *root, CustomPath *best_path)
+{
+	CustomPlan	   *custom_plan;
+	RelOptInfo	   *rel;
+	List		   *tlist = NIL;
+	List		   *clauses = NIL;
+
+	/*
+	 * Create a custom-plan object delivered from CustomPlan type,
+	 * according to the supplied CustomPath
+	 */
+	Assert(best_path->path.pathtype == T_CustomPlan ||
+		   best_path->path.pathtype == T_CustomPlanMarkPos);
+	custom_plan = best_path->methods->CreateCustomPlan(root, best_path);
+	Assert(nodeTag(custom_plan) == best_path->path.pathtype);
+
+	rel = best_path->path.parent;
+	if (rel)
+	{
+		if (rel->reloptkind == RELOPT_BASEREL)
+		{
+			Assert(rel->relid > 0);
+			custom_plan->scan.scanrelid = rel->relid;
+
+			/*
+			 * For table scans, rather than using the relation targetlist
+			 * (which is only those Vars actually needed by the query),
+			 * we prefer to generate a tlist containing all Vars in order.
+			 * This will allow the executor to optimize away projection of
+			 * the table tuples, if possible.
+			 */
+			if (use_physical_tlist(root, rel))
+				tlist = build_physical_tlist(root, rel);
+		}
+		/* elsewhere, we generate a tlist from the relation targetlist */
+		if (tlist == NIL)
+			tlist = build_path_tlist(root, &best_path->path);
+
+		/*
+		 * Extract the relevant restriction clauses from the parent relation.
+		 * The executor must apply all these restrictions during the scan,
+		 * except for pseudoconstants which we'll take care of below.
+		 */
+		clauses = rel->baserestrictinfo;
+
+		/*
+		 * If this is a parameterized scan, we also need to enforce all
+		 * the join clauses available from the outer relation(s).
+		 */
+		if (best_path->path.param_info)
+			clauses = list_concat(list_copy(clauses),
+								  best_path->path.param_info->ppi_clauses);
+
+		/* Sort clauses into best execution order */
+		clauses = order_qual_clauses(root, clauses);
+
+		/*
+		 * Replace outer-relation variables with nestloop params.
+		 * Note that any other clauses which is managed by extension
+		 * itself has to be handled in InitCustomPlan() method, as
+		 * built-in code doing.
+		 */
+		if (best_path->path.param_info)
+			clauses = (List *)replace_nestloop_params(root, (Node *)clauses);
+	}
+	/*
+	 * Copy cost data from Path to Plan; no need to make custom-plan
+	 * providers do this
+	 */
+	copy_path_costsize((Plan *)custom_plan, (Path *)best_path);
+
+	/*
+	 * Let the custom-plan provider perform its final initialization
+	 * of this CustomPlan (to be an inherited type, actually) node
+	 * according to its own necessity.
+	 * Note that custom-plan provider may/can replace (or stack another
+	 * one on) its own custom-plan node on demand, for example, to add
+	 * Result node to handle pseudo constant using create_gating_plan().
+	 */
+	return custom_plan->methods->InitCustomPlan(custom_plan,
+												root, best_path,
+												tlist, clauses);
+}
 
 /*****************************************************************************
  *
@@ -2540,7 +2633,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 768c5c7..627946e 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);
@@ -576,6 +575,14 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 			}
 			break;
 
+		case T_CustomPlan:
+			{
+				CustomPlan *cplan = (CustomPlan *) plan;
+
+				cplan->methods->SetCustomPlanRef(root, cplan, rtoffset);
+			}
+			break;
+
 		case T_NestLoop:
 		case T_MergeJoin:
 		case T_HashJoin:
@@ -1126,7 +1133,7 @@ fix_expr_common(PlannerInfo *root, Node *node)
  * 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 be92049..070634f 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -2236,6 +2236,21 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
 			context.paramids = bms_add_members(context.paramids, scan_params);
 			break;
 
+		case T_CustomPlan:
+			{
+				CustomPlan *cplan = (CustomPlan *) plan;
+
+				context.paramids =
+					cplan->methods->FinalizeCustomPlan(root,
+													   cplan,
+													   context.paramids,
+													   valid_params,
+													   scan_params,
+													   finalize_primnode,
+													   (void *)&context);
+			}
+			break;
+
 		case T_ModifyTable:
 			{
 				ModifyTable *mtplan = (ModifyTable *) plan;
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 4e05dcd..1e4150f 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -16,6 +16,10 @@
 
 #include <math.h>
 
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "catalog/pg_custom_plan.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
@@ -25,8 +29,11 @@
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
+#include "utils/inval.h"
 #include "utils/lsyscache.h"
+#include "utils/memutils.h"
 #include "utils/selfuncs.h"
+#include "utils/syscache.h"
 
 
 typedef enum
@@ -2067,3 +2074,106 @@ reparameterize_path(PlannerInfo *root, Path *path,
 	}
 	return NULL;
 }
+
+/*****************************************************************************
+ *		creation of custom-plan paths
+ *****************************************************************************/
+static List *custom_scan_callchain = NIL;
+static bool custom_plan_callchain_is_ready = false;
+static MemoryContext custom_plan_memcxt = NULL;
+
+static void
+invalidate_custom_plan_callchain(Datum arg, int cacheid, uint32 hashvalue)
+{
+	MemoryContextReset(custom_plan_memcxt);
+	custom_plan_callchain_is_ready = false;
+	custom_scan_callchain = NIL;
+}
+
+static void
+setup_custom_plan_callchain(void)
+{
+	Relation		rel;
+	SysScanDesc		scan;
+	HeapTuple		tuple;
+	MemoryContext	oldcxt;
+
+	custom_scan_callchain = NIL;
+
+	rel = heap_open(CustomPlanRelationId, AccessShareLock);
+
+	/* full scan on the pg_custom_plan once */
+	scan = systable_beginscan(rel, InvalidOid, false, NULL, 0, NULL);
+
+	oldcxt = MemoryContextSwitchTo(custom_plan_memcxt);
+	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+	{
+		Form_pg_custom_plan	custForm
+			= (Form_pg_custom_plan) GETSTRUCT(tuple);
+
+		if ((custForm->custclass & CUSTOMPLAN_CLASS_SCAN) != 0)
+		{
+			custom_scan_callchain = lappend_oid(custom_scan_callchain,
+												custForm->custprovider);
+		}
+		else
+			elog(LOG, "Bug? custom-plan \"%s\" has unknown class: %c",
+				 NameStr(custForm->custname), custForm->custclass);
+	}
+	MemoryContextSwitchTo(oldcxt);
+	systable_endscan(scan);
+
+	heap_close(rel, AccessShareLock);
+
+	custom_plan_callchain_is_ready = true;
+}
+
+static void
+init_custom_plan_callchain(void)
+{
+	/* memory context to keep callchain for custom-plans */
+	custom_plan_memcxt = AllocSetContextCreate(CacheMemoryContext,
+											   "custom plan memory context",
+											   ALLOCSET_DEFAULT_MINSIZE,
+											   ALLOCSET_DEFAULT_INITSIZE,
+											   ALLOCSET_DEFAULT_MAXSIZE);
+
+	/* flush cached callchain on catalog updates */
+	CacheRegisterSyscacheCallback(CUSTOMPLANOID,
+								  invalidate_custom_plan_callchain,
+								  (Datum) 0);
+	/* also, initial setting up */
+	setup_custom_plan_callchain();
+}
+
+/*
+ * call_custom_scan_providers
+ *
+ * A callchain on relation scan. custom-plan provider can add alternative
+ * scan paths delivered from CustomPath class.
+ */
+void
+call_custom_scan_providers(PlannerInfo *root,
+						   RelOptInfo *baserel,
+						   RangeTblEntry *rte)
+{
+	customScanArg	sarg;
+	ListCell	   *cell;
+
+	if (!custom_plan_memcxt)
+		init_custom_plan_callchain();
+	else if (!custom_plan_callchain_is_ready)
+		setup_custom_plan_callchain();
+
+	Assert(custom_plan_callchain_is_ready);
+	sarg.custom_class = CUSTOMPLAN_CLASS_SCAN;
+	sarg.root = root;
+	sarg.baserel = baserel;
+	sarg.rte = rte;
+
+	foreach (cell, custom_scan_callchain)
+	{
+		(void) OidFunctionCall1(lfirst_oid(cell),
+								PointerGetDatum(&sarg));
+	}
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index dd04b1a..e50e92f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -51,6 +51,7 @@
 
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/pg_custom_plan.h"
 #include "catalog/pg_trigger.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -251,6 +252,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		DropOwnedStmt ReassignOwnedStmt
 		AlterTSConfigurationStmt AlterTSDictionaryStmt
 		CreateMatViewStmt RefreshMatViewStmt
+		CreateCustomPlanStmt DropCustomPlanStmt
 
 %type <node>	select_no_parens select_with_parens select_clause
 				simple_select values_clause
@@ -503,6 +505,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
 
+%type <ival>	custom_class
+%type <defelt>	custom_option
+%type <list>	opt_custom_options custom_options
+
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -538,7 +544,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
 	CROSS CSV CURRENT_P
 	CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
-	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
+	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CUSTOM CYCLE
 
 	DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
 	DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DESC
@@ -577,8 +583,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	OBJECT_P OF OFF OFFSET OIDS ON ONLY OPERATOR OPTION OPTIONS OR
 	ORDER ORDINALITY OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER
 
-	PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POSITION
-	PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
+	PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLAN PLANS POSITION
+	PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY PROVIDER
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROGRAM
 
 	QUOTE
@@ -588,8 +594,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK
 	ROW ROWS RULE
 
-	SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
-	SERIALIZABLE SERVER SESSION SESSION_USER SET SETOF SHARE
+	SAVEPOINT SCAN SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE
+	SEQUENCES SERIALIZABLE SERVER SESSION SESSION_USER SET SETOF SHARE
 	SHOW SIMILAR SIMPLE SMALLINT SNAPSHOT SOME STABLE STANDALONE_P START
 	STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBSTRING
 	SYMMETRIC SYSID SYSTEM_P
@@ -751,6 +757,7 @@ stmt :
 			| CreateAssertStmt
 			| CreateCastStmt
 			| CreateConversionStmt
+			| CreateCustomPlanStmt
 			| CreateDomainStmt
 			| CreateExtensionStmt
 			| CreateFdwStmt
@@ -781,6 +788,7 @@ stmt :
 			| DoStmt
 			| DropAssertStmt
 			| DropCastStmt
+			| DropCustomPlanStmt
 			| DropFdwStmt
 			| DropForeignServerStmt
 			| DropGroupStmt
@@ -8679,6 +8687,77 @@ CreateConversionStmt:
 			}
 		;
 
+/****************************************************************************
+ *
+ *	QUERY:
+ *			CREATE CUSTOM PLAN name <options>
+ *
+ ****************************************************************************/
+
+CreateCustomPlanStmt:
+			CREATE CUSTOM PLAN name FOR custom_class opt_custom_options
+			{
+				CreateCustomPlanStmt *n = makeNode(CreateCustomPlanStmt);
+				n->custom_name = $4;
+				n->custom_class = $6;
+				n->custom_options = $7;
+				$$ = (Node *) n;
+			}
+		;
+
+custom_class:
+			SCAN				{ $$ = CUSTOMPLAN_CLASS_SCAN; }
+		;
+
+custom_option:
+			PROVIDER handler_name
+			{
+				$$ = makeDefElem("provider", (Node *)$2);
+			}
+		;
+
+custom_options:
+			custom_option					{ $$ = list_make1($1); }
+			| custom_options custom_option	{ $$ = lappend($1, $2); }
+		;
+
+opt_custom_options:
+			custom_options		{ $$ = $1; }
+			| /* empty */		{ $$ = NIL; }
+		;
+
+/****************************************************************************
+ *
+ *     QUERY:
+ *             DROP CUSTOM PLAN name
+ *
+ ****************************************************************************/
+
+DropCustomPlanStmt:
+			DROP CUSTOM PLAN name opt_drop_behavior
+			{
+				DropStmt *n = makeNode(DropStmt);
+				n->removeType = OBJECT_CUSTOM_PLAN;
+				n->objects = list_make1(list_make1(makeString($4)));
+				n->arguments = NIL;
+				n->missing_ok = false;
+				n->behavior = $5;
+				n->concurrent = false;
+				$$ = (Node *) n;
+			}
+		|	DROP CUSTOM PLAN IF_P EXISTS name opt_drop_behavior
+			{
+				DropStmt *n = makeNode(DropStmt);
+				n->removeType = OBJECT_CUSTOM_PLAN;
+				n->objects = list_make1(list_make1(makeString($6)));
+				n->arguments = NIL;
+				n->missing_ok = true;
+				n->behavior = $7;
+				n->concurrent = false;
+				$$ = (Node *) n;
+			}
+		;
+
 /*****************************************************************************
  *
  *		QUERY:
@@ -12842,6 +12921,7 @@ unreserved_keyword:
 			| CSV
 			| CURRENT_P
 			| CURSOR
+			| CUSTOM
 			| CYCLE
 			| DATA_P
 			| DATABASE
@@ -12954,6 +13034,7 @@ unreserved_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PLAN
 			| PLANS
 			| PRECEDING
 			| PREPARE
@@ -12964,6 +13045,7 @@ unreserved_keyword:
 			| PROCEDURAL
 			| PROCEDURE
 			| PROGRAM
+			| PROVIDER
 			| QUOTE
 			| RANGE
 			| READ
@@ -12989,6 +13071,7 @@ unreserved_keyword:
 			| ROWS
 			| RULE
 			| SAVEPOINT
+			| SCAN
 			| SCHEMA
 			| SCROLL
 			| SEARCH
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 3423898..3aa7d5f 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -202,6 +202,7 @@ check_xact_readonly(Node *parsetree)
 		case T_AlterTableSpaceOptionsStmt:
 		case T_AlterTableSpaceMoveStmt:
 		case T_CreateForeignTableStmt:
+		case T_CreateCustomPlanStmt:
 		case T_SecLabelStmt:
 			PreventCommandIfReadOnly(CreateCommandTag(parsetree));
 			break;
@@ -683,6 +684,10 @@ standard_ProcessUtility(Node *parsetree,
 			AlterEventTrigger((AlterEventTrigStmt *) parsetree);
 			break;
 
+		case  T_CreateCustomPlanStmt:
+			DefineCustomPlan((CreateCustomPlanStmt *) parsetree);
+			break;
+
 			/*
 			 * ******************************** ROLE statements ****
 			 */
@@ -1940,6 +1945,9 @@ CreateCommandTag(Node *parsetree)
 				case OBJECT_OPFAMILY:
 					tag = "DROP OPERATOR FAMILY";
 					break;
+				case OBJECT_CUSTOM_PLAN:
+					tag = "DROP CUSTOM PLAN";
+					break;
 				default:
 					tag = "???";
 			}
@@ -2207,6 +2215,10 @@ CreateCommandTag(Node *parsetree)
 			tag = "ALTER EVENT TRIGGER";
 			break;
 
+		case T_CreateCustomPlanStmt:
+			tag = "CREATE CUSTOM PLAN";
+			break;
+
 		case T_CreatePLangStmt:
 			tag = "CREATE LANGUAGE";
 			break;
@@ -2830,6 +2842,10 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_CreateCustomPlanStmt:
+			lev = LOGSTMT_DDL;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index a30d8fe..f4c5d92 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -5387,6 +5387,24 @@ 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-plan provider replaced a varno in expression tree by special
+ * varno.
+ */
+static Node *
+GetSpecialCustomVar(PlanState *ps, Var *varnode)
+{
+	CustomPlanState *cps = (CustomPlanState *) ps;
+
+	Assert(IsA(ps, CustomPlanState));
+	Assert(IS_SPECIAL_VARNO(varnode->varno));
+
+	return (Node *)cps->methods->GetSpecialCustomVar(cps, varnode);
+}
 
 /*
  * Display a Var appropriately.
@@ -5416,6 +5434,7 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
 	int			netlevelsup;
 	deparse_namespace *dpns;
 	deparse_columns *colinfo;
+	Node	   *expr;
 	char	   *refname;
 	char	   *attname;
 
@@ -5440,6 +5459,21 @@ 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, CustomPlanState) &&
+			 (expr = GetSpecialCustomVar(dpns->planstate, var)) != NULL)
+	{
+		/*
+		 * 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, ')');
+		return NULL;
+	}
 	else if (var->varno == OUTER_VAR && dpns->outer_tlist)
 	{
 		TargetEntry *tle;
@@ -5728,6 +5762,26 @@ 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, CustomPlanState) &&
+			 (expr = GetSpecialCustomVar(dpns->planstate, var)) != NULL)
+	{
+		StringInfo		saved = context->buf;
+		StringInfoData	temp;
+
+		initStringInfo(&temp);
+		context->buf = &temp;
+
+		if (!IsA(expr, Var))
+			appendStringInfoChar(context->buf, '(');
+		get_rule_expr((Node *) expr, context, true);
+		if (!IsA(expr, Var))
+			appendStringInfoChar(context->buf, ')');
+
+		context->buf = saved;
+
+		return temp.data;
+	}
 	else if (var->varno == OUTER_VAR && dpns->outer_tlist)
 	{
 		TargetEntry *tle;
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 94d951c..20d7767 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -32,6 +32,7 @@
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_conversion.h"
+#include "catalog/pg_custom_plan.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_db_role_setting.h"
 #include "catalog/pg_default_acl.h"
@@ -345,6 +346,28 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		8
 	},
+	{CustomPlanRelationId,		/* CUSTOMPLANOID */
+		CustomPlanOidIndexId,
+		1,
+		{
+			ObjectIdAttributeNumber,
+			0,
+			0,
+			0
+		},
+		32
+	},
+	{CustomPlanRelationId,		/* CUSTOMPLANNAME */
+		CustomPlanNameIndexId,
+		1,
+		{
+			Anum_pg_custom_plan_custname,
+			0,
+			0,
+			0,
+		},
+		32
+	},
 	{DatabaseRelationId,		/* DATABASEOID */
 		DatabaseOidIndexId,
 		1,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 8ed2592..56f5371 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -147,6 +147,7 @@ typedef enum ObjectClass
 	OCLASS_DEFACL,				/* pg_default_acl */
 	OCLASS_EXTENSION,			/* pg_extension */
 	OCLASS_EVENT_TRIGGER,		/* pg_event_trigger */
+	OCLASS_CUSTOM_PLAN,			/* pg_custom_plan */
 	MAX_OCLASS					/* MUST BE LAST */
 } ObjectClass;
 
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 0515b75..a5e6cea 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -313,6 +313,12 @@ DECLARE_UNIQUE_INDEX(pg_extension_name_index, 3081, on pg_extension using btree(
 DECLARE_UNIQUE_INDEX(pg_range_rngtypid_index, 3542, on pg_range using btree(rngtypid oid_ops));
 #define RangeTypidIndexId					3542
 
+DECLARE_UNIQUE_INDEX(pg_custom_plan_oid_index, 3563, on pg_custom_plan using btree(oid oid_ops));
+#define CustomPlanOidIndexId 3563
+
+DECLARE_UNIQUE_INDEX(pg_custom_plan_name_index, 3564, on pg_custom_plan using btree(custname name_ops));
+#define CustomPlanNameIndexId 3564
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_custom_plan.h b/src/include/catalog/pg_custom_plan.h
new file mode 100644
index 0000000..5b31a72
--- /dev/null
+++ b/src/include/catalog/pg_custom_plan.h
@@ -0,0 +1,49 @@
+/* -------------------------------------------------------------------------
+ *
+ * pg_custom_plan.h
+ *	definition of the system "custom plan" relation (pg_custom_plan)
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * -------------------------------------------------------------------------
+ */
+#ifndef PG_CUSTOM_PLAN_H
+#define PG_CUSTOM_PLAN_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *     pg_custom_plan definition.  cpp turns this into
+ *     typedef struct FormData_pg_custom_plan
+ * ----------------
+ */
+#define CustomPlanRelationId       3562
+
+CATALOG(pg_custom_plan,3562)
+{
+	NameData	custname;		/* name of custom-plan provider */
+	char		custclass;		/* class of custom-plan */
+	Oid			custprovider;	/* function of custom-plan provider */
+} FormData_pg_custom_plan;
+
+/* ----------------
+ *     Form_pg_custom_plan corresponds to a pointer to a tuple
+ *     with the format of pg_custom_plan relation.
+ * ----------------
+ */
+typedef FormData_pg_custom_plan *Form_pg_custom_plan;
+
+/* ----------------
+ *     compiler constants for pg_custom_plan
+ * ----------------
+ */
+#define Natts_pg_custom_plan				3
+#define Anum_pg_custom_plan_custname		1
+#define Anum_pg_custom_plan_custclass		2
+#define Anum_pg_custom_plan_custprovider	3
+
+/* definition of custclass */
+#define CUSTOMPLAN_CLASS_SCAN			's'
+
+#endif	/* PG_CUSTOM_PLAN_H */
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index 87ee4eb..942c22a 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");
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 5ec9374..ee36614 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -129,6 +129,11 @@ extern Datum transformGenericOptions(Oid catalogId,
 						List *options,
 						Oid fdwvalidator);
 
+/* commands/custom_plan.c */
+extern Oid get_custom_plan_oid(const char *custom_name, bool missing_ok);
+extern void	RemoveCustomPlanById(Oid cust_oid);
+extern Oid	DefineCustomPlan(CreateCustomPlanStmt *stmt);
+
 /* support routines in commands/define.c */
 
 extern char *defGetString(DefElem *def);
diff --git a/src/include/executor/nodeCustom.h b/src/include/executor/nodeCustom.h
new file mode 100644
index 0000000..50a4edb
--- /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 CustomPlanState *ExecInitCustomPlan(CustomPlan *cplan,
+										   EState *estate, int eflags);
+extern TupleTableSlot *ExecCustomPlan(CustomPlanState *node);
+extern Node *MultiExecCustomPlan(CustomPlanState *node);
+extern void ExecEndCustomPlan(CustomPlanState *node);
+
+extern void ExecReScanCustomPlan(CustomPlanState *node);
+extern void ExecCustomMarkPos(CustomPlanState *node);
+extern void ExecCustomRestrPos(CustomPlanState *node);
+
+#endif	/* NODECUSTOM_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 0ab2a13..06710a9 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1501,6 +1501,47 @@ typedef struct ForeignScanState
 	void	   *fdw_state;		/* foreign-data wrapper can keep state here */
 } ForeignScanState;
 
+/* ----------------
+ * CustomPlanState information
+ *
+ *		CustomPlan nodes are used to execute custom code within executor.
+ * ----------------
+ */
+struct CustomExecMethods;
+struct ExplainState;	/* to avoid to include explain.h here */
+
+typedef struct CustomPlanState
+{
+	ScanState	ss;
+	const struct CustomExecMethods *methods;
+} CustomPlanState;
+
+typedef struct CustomExecMethods
+{
+	const char     *CustomName;
+
+	/* EXECUTOR methods */
+	void    (*BeginCustomPlan)(CustomPlanState *node,
+							   EState *estate,
+							   int eflags);
+	TupleTableSlot *(*ExecCustomPlan)(CustomPlanState *node);
+	Node   *(*MultiExecCustomPlan)(CustomPlanState *node);
+	void	(*EndCustomPlan)(CustomPlanState *node);
+	void	(*ReScanCustomPlan)(CustomPlanState *node);
+	void	(*MarkPosCustomPlan)(CustomPlanState *node);
+	void	(*RestrPosCustomPlan)(CustomPlanState *node);
+
+	/* EXPLAIN support */
+	void	(*ExplainCustomPlanTargetRel)(CustomPlanState *node,
+										  struct ExplainState *es);
+	void    (*ExplainCustomPlan)(CustomPlanState *node,
+								 List *ancestors,
+								 struct ExplainState *es);
+	void	(*ExplainCustomPreScanNode)(CustomPlanState *node,
+										Bitmapset **rels_used);
+	Node   *(*GetSpecialCustomVar)(CustomPlanState *node, Var *varnode);
+} CustomExecMethods;
+
 /* ----------------------------------------------------------------
  *				 Join State Information
  * ----------------------------------------------------------------
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index bc58e16..99e9b2d 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -62,6 +62,8 @@ typedef enum NodeTag
 	T_CteScan,
 	T_WorkTableScan,
 	T_ForeignScan,
+	T_CustomPlan,
+	T_CustomPlanMarkPos,
 	T_Join,
 	T_NestLoop,
 	T_MergeJoin,
@@ -107,6 +109,7 @@ typedef enum NodeTag
 	T_CteScanState,
 	T_WorkTableScanState,
 	T_ForeignScanState,
+	T_CustomPlanState,
 	T_JoinState,
 	T_NestLoopState,
 	T_MergeJoinState,
@@ -224,6 +227,7 @@ typedef enum NodeTag
 	T_HashPath,
 	T_TidPath,
 	T_ForeignPath,
+	T_CustomPath,
 	T_AppendPath,
 	T_MergeAppendPath,
 	T_ResultPath,
@@ -365,6 +369,7 @@ typedef enum NodeTag
 	T_RefreshMatViewStmt,
 	T_ReplicaIdentityStmt,
 	T_AlterSystemStmt,
+	T_CreateCustomPlanStmt,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 9a68c87..e09ae38 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1192,6 +1192,7 @@ typedef enum ObjectType
 	OBJECT_CONSTRAINT,
 	OBJECT_COLLATION,
 	OBJECT_CONVERSION,
+	OBJECT_CUSTOM_PLAN,
 	OBJECT_DATABASE,
 	OBJECT_DOMAIN,
 	OBJECT_EVENT_TRIGGER,
@@ -2033,6 +2034,18 @@ typedef struct AlterOpFamilyStmt
 } AlterOpFamilyStmt;
 
 /* ----------------------
+ *     Create Custom Plan Statement
+ * ----------------------
+ */
+typedef struct CreateCustomPlanStmt
+{
+	NodeTag     type;
+	char	   *custom_name;		/* name of custom-plan provider */
+	char		custom_class;		/* class of custom-plan provides */
+	List	   *custom_options;		/* generic options for provider */
+} CreateCustomPlanStmt;
+
+/* ----------------------
  *		Drop Table|Sequence|View|Index|Type|Domain|Conversion|Schema Statement
  * ----------------------
  */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 3b9c683..0b01580 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -15,6 +15,7 @@
 #define PLANNODES_H
 
 #include "access/sdir.h"
+#include "lib/stringinfo.h"
 #include "nodes/bitmapset.h"
 #include "nodes/primnodes.h"
 
@@ -479,6 +480,45 @@ typedef struct ForeignScan
 	bool		fsSystemCol;	/* true if any "system column" is needed */
 } ForeignScan;
 
+/* ----------------
+ *     CustomPlan node
+ * ----------------
+ */
+struct CustomPlanMethods;
+struct CustomPlanState;		/* to avoid to include nodes/execnodes.h here */
+struct CustomPath;			/* to avoid to include nodes/relation.h here */
+struct PlannerInfo;			/* to avoid to include nodes/relation.h here */
+
+typedef struct CustomPlan
+{
+	Scan		scan;
+	const struct CustomPlanMethods *methods;
+} CustomPlan;
+
+typedef struct CustomPlanMethods
+{
+	const char *CustomName;
+	Plan	  *(*InitCustomPlan)(CustomPlan *custom_plan,
+								 struct PlannerInfo *root,
+								 struct CustomPath *best_path,
+								 List *tlist,
+								 List *clauses);
+	void	   (*SetCustomPlanRef)(struct PlannerInfo *root,
+								   CustomPlan *custom_plan,
+								   int rtoffset);
+	bool	   (*SupportBackwardScan)(CustomPlan *custom_plan);
+	Bitmapset *(*FinalizeCustomPlan)(struct PlannerInfo *root,
+									 CustomPlan *custom_plan,
+									 Bitmapset *paramids,
+									 Bitmapset *valid_params,
+									 Bitmapset *scan_params,
+									 bool (*finalize_primnode)(),
+									 void *finalize_context);
+	struct CustomPlanState *(*CreateCustomPlanState)(CustomPlan *custom_plan);
+	void	   (*TextOutCustomPlan)(StringInfo str,
+									const CustomPlan *node);
+	CustomPlan *(*CopyCustomPlan)(const CustomPlan *from);
+} CustomPlanMethods;
 
 /*
  * ==========
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 300136e..b0e7c36 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"
@@ -878,6 +879,33 @@ 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;		/* to avoid to include plannode.h here */
+struct CustomPlan;	/* to avoid to include plannode.h here */
+
+typedef struct CustomPath
+{
+	Path        path;
+	const struct CustomPathMethods *methods;
+} CustomPath;
+
+typedef struct CustomPathMethods
+{
+	const char *CustomName;
+	struct CustomPlan *(*CreateCustomPlan)(PlannerInfo *root,
+										   CustomPath *best_path);
+	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..df4d6e8 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -129,6 +129,20 @@ extern Path *reparameterize_path(PlannerInfo *root, Path *path,
 					double loop_count);
 
 /*
+ * interface towards custom-plan provider functions
+ */
+typedef struct {
+	uint32			custom_class;
+	PlannerInfo	   *root;
+	RelOptInfo	   *baserel;
+	RangeTblEntry  *rte;
+} customScanArg;
+
+extern void call_custom_scan_providers(PlannerInfo *root,
+									   RelOptInfo *baserel,
+									   RangeTblEntry *rte);
+
+/*
  * prototypes for relnode.c
  */
 extern void setup_simple_rel_arrays(PlannerInfo *root);
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 8bdb7db..76e3c86 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
@@ -128,6 +129,7 @@ extern List *remove_useless_joins(PlannerInfo *root, List *joinlist);
  */
 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 set_opfuncid(OpExpr *opexpr);
 extern void set_sa_opfuncid(ScalarArrayOpExpr *opexpr);
 extern void record_plan_function_dependency(PlannerInfo *root, Oid funcid);
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 61fae22..9851fe9 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -107,6 +107,7 @@ PG_KEYWORD("current_time", CURRENT_TIME, RESERVED_KEYWORD)
 PG_KEYWORD("current_timestamp", CURRENT_TIMESTAMP, RESERVED_KEYWORD)
 PG_KEYWORD("current_user", CURRENT_USER, RESERVED_KEYWORD)
 PG_KEYWORD("cursor", CURSOR, UNRESERVED_KEYWORD)
+PG_KEYWORD("custom", CUSTOM, UNRESERVED_KEYWORD)
 PG_KEYWORD("cycle", CYCLE, UNRESERVED_KEYWORD)
 PG_KEYWORD("data", DATA_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("database", DATABASE, UNRESERVED_KEYWORD)
@@ -283,6 +284,7 @@ PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD)
 PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD)
+PG_KEYWORD("plan", PLAN, UNRESERVED_KEYWORD)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD)
 PG_KEYWORD("position", POSITION, COL_NAME_KEYWORD)
 PG_KEYWORD("preceding", PRECEDING, UNRESERVED_KEYWORD)
@@ -296,6 +298,7 @@ PG_KEYWORD("privileges", PRIVILEGES, UNRESERVED_KEYWORD)
 PG_KEYWORD("procedural", PROCEDURAL, UNRESERVED_KEYWORD)
 PG_KEYWORD("procedure", PROCEDURE, UNRESERVED_KEYWORD)
 PG_KEYWORD("program", PROGRAM, UNRESERVED_KEYWORD)
+PG_KEYWORD("provider", PROVIDER, UNRESERVED_KEYWORD)
 PG_KEYWORD("quote", QUOTE, UNRESERVED_KEYWORD)
 PG_KEYWORD("range", RANGE, UNRESERVED_KEYWORD)
 PG_KEYWORD("read", READ, UNRESERVED_KEYWORD)
@@ -326,6 +329,7 @@ PG_KEYWORD("row", ROW, COL_NAME_KEYWORD)
 PG_KEYWORD("rows", ROWS, UNRESERVED_KEYWORD)
 PG_KEYWORD("rule", RULE, UNRESERVED_KEYWORD)
 PG_KEYWORD("savepoint", SAVEPOINT, UNRESERVED_KEYWORD)
+PG_KEYWORD("scan", SCAN, UNRESERVED_KEYWORD)
 PG_KEYWORD("schema", SCHEMA, UNRESERVED_KEYWORD)
 PG_KEYWORD("scroll", SCROLL, UNRESERVED_KEYWORD)
 PG_KEYWORD("search", SEARCH, UNRESERVED_KEYWORD)
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index f97229f..7272eec 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -52,6 +52,8 @@ enum SysCacheIdentifier
 	CONNAMENSP,
 	CONSTROID,
 	CONVOID,
+	CUSTOMPLANOID,
+	CUSTOMPLANNAME,
 	DATABASEOID,
 	DEFACLROLENSPOBJ,
 	ENUMOID,
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 111d24c..e3cad45 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -97,6 +97,7 @@ pg_class|t
 pg_collation|t
 pg_constraint|t
 pg_conversion|t
+pg_custom_plan|t
 pg_database|t
 pg_db_role_setting|t
 pg_default_acl|t
#4Shigeru Hanada
shigeru.hanada@gmail.com
In reply to: Kohei KaiGai (#3)

Kaigai-san,

Sorry for lagged response.

Here are my random thoughts about the patch. I couldn't understand
the patch fully, because some of APIs are not used by ctidscan. If

Custom Scan patch v2 review

* Custom plan class comparison
In backend/optimizer/util/pathnode.c, custclass is compared by bit-and
with 's'. Do you plan to use custclass as bit field? If so, values
for custom plan class should not be a character. Otherwise, custclass
should be compared by == operator.

* Purpose of GetSpecialCustomVar()
The reason why FinalizeCustomPlan callback is necessary is not clear to me.
Could you show a case that the API would be useful?

* Purpose of FinalizeCustomPlan()
The reason why FinalizeCustomPlan callback is necessary is not clear
to me, because ctidscan just calls finalize_primnode() and
bms_add_members() with given information. Could you show a case that
the API would be useful?

* Is it ok to call set_cheapest() for all relkind?
Now set_cheapest() is called not for only relation and foreign table
but also custom plan, and other relations such as subquery, function,
and value. Calling call_custom_scan_provider() and set_cheapest() in
the case of RTE_RELATION seems similar to the old construct, how do
you think about this?

* Is it hard to get rid of CopyCustomPlan()?
Copying ForeignScan node doesn't need per-FDW copy function by
limiting fdw_private to have only copy-able objects. Can't we use the
same way for CustomPlan? Letting authors call NodeSetTag or
copyObject() sounds uncomfortable to me.

This would be able to apply to TextOutCustomPlan() and TextOutCustomPath() too.

* MultiExec support is appropriate for the first version?
The cases need MultiExec seems little complex for the first version of
custom scan. What kind of plan do you image for this feature?

* Does SupportBackwardScan() have enough information?
Other scans check target list with TargetListSupportsBackwardScan().
Isn't it necessary to check it for CustomPlan too in
ExecSupportsBackwardScan()?

* Place to call custom plan provider
Is it necessary to call provider when relkind != RELKIND_RELATION? If
yes, isn't it necessary to call for append relation?

I know that we concentrate to replacing scan in the initial version,
so it would not be a serious problem, but it would be good to consider
extensible design.

* Custom Plan Provider is "addpath"?
Passing addpath handler as only one attribute of CUSTOM PLAN PROVIDER
seems little odd.
Using handler like FDW makes the design too complex and/or messy?

* superclass of CustomPlanState
CustomPlanState derives ScanState, instead of deriving PlanState
directly. I worry the case of non-heap-scan custom plan, but it might
be ok to postpone consideration about that at the first cut.

* Naming and granularity of objects related to custom plan
I'm not sure the current naming is appropriate, especially difference
between "custom plan" and "provider" and "handler". In the context of
CREATE CUSTOM PLAN statement, what the term "custom plan" means? My
impression is that "custom plan" is an alternative plan type, e.g.
ctidscan or pg_strom_scan. Then what the term "provider" means? My
impression for that is extension, such as ctidscan and pg_strom. The
grammar allows users to pass function via PROVIDER clause of CREATE
CUSTOM SCAN, so the function would be the provider of the custom plan
created by the statement.

* enable_customscan
GUC parameter enable_customscan would be useful for users who want to
disable custom plan feature temporarily. In the case of pg_strom,
using GPU for limited sessions for analytic or batch applications
seems handy.

* Adding pg_custom_plan catalog
Using "cust" as prefix for pg_custom_plan causes ambiguousness which
makes it difficult to choose catalog prefix for a feature named
"Custom Foo" in future. How about using "cusp" (CUStom Plan)?

Or is it better to use pg_custom_plan_provider as catalog relation
name, as the document says that "CREATE CUSTOM PLAN defines custom
plan provider". Then prefix could be "cpp" (Custom Plan Provider).
This seems to match the wording used for pg_foreign_data_wrapper.

* CREATE CUSTOM PLAN statement
This is just a question: We need to emit CREATE CUSTOM PLAN if we want to use
I wonder how it is extended when supporting join as custom class.

* New operators about TID comparison
IMO this portion should be a separated patch, because it adds OID
definition of existing operators such as tidgt and tidle. Is there
any (explicit or implicit) rule about defining macro for oid of an
operator?

* Prototype of get_custom_plan_oid()
custname (or cppname if use the rule I proposed above) seems
appropriate as the parameter name of get_custom_plan_oid() because
similar funcitons use catalog column names in such case.

* Coding conventions
Some lines are indented with white space. Future pgindent run will
fix this issue?

* Unnecessary struct forward declaration
Forward declarations of CustomPathMethods, Plan, and CustomPlan in
includes/nodes/relation.h seem unncecessary. Other headers might have
same issue.

* Unnecessary externing of replace_nestloop_params()
replace_nestloop_params() is extern-ed but it's never called outside
createplan.c.

* Externing fix_scan_expr()
If it's necessary for all custom plan providers to call fix_scan_expr
(via fix_scan_list macro), isn't it able to do it in set_plan_refs()
before calling SetCustomPlanRef()?

* What does T_CustomPlanMarkPos mean?
It's not clear to me when CustomPlanMarkPos works. Is it for a custom
plan provider which supports marking position and rewind to the
position, and ctidscan just lacks capability to do that, so it is not
used anywhere?

* Unnecessary changes in allpaths.c
some comment about Subquery and CTE are changed (perhaps) accidentally.

* Typos
* planenr -> planner, implements -> implement in create_custom_plan.sgml
* CustomScan in nodeCustom.h should be CustomPlan?
* delivered -> derived, in src/backend/optimizer/util/pathnode.c

* Document "Writing Custom Plan Provider" is not provided
Custom Plan Provider author would (and I DO!) hope documents about
writing a custom plan provider.

Regards,

2014-06-17 23:12 GMT+09:00 Kohei KaiGai <kaigai@kaigai.gr.jp>:

Hanada-san,

Thanks for your checks. I oversight the points when I submit the patch, sorry.
The attached one is revised one on documentation stuff and contrib/Makefile.

Thanks,

2014-06-16 17:29 GMT+09:00 Shigeru Hanada <shigeru.hanada@gmail.com>:

Kaigai-san,

I've just applied v1 patch, and tried build and install, but I found two issues:

1) The contrib/ctidscan is not automatically built/installed because
it's not described in contrib/Makefile. Is this expected behavior?
2) I got an error message below when building document.

$ cd doc/src/sgml
$ make
openjade -wall -wno-unused-param -wno-empty -wfully-tagged -D . -D .
-d stylesheet.dsl -t sgml -i output-html -V html-index postgres.sgml
openjade:catalogs.sgml:2525:45:X: reference to non-existent ID
"SQL-CREATECUSTOMPLAN"
make: *** [HTML.index] Error 1
make: *** Deleting file `HTML.index'

I'll review another part of the patch, including the design.

2014-06-14 10:59 GMT+09:00 Kohei KaiGai <kaigai@kaigai.gr.jp>:

According to the discussion upthread, I revised the custom-plan patch
to focus on regular relation scan but no join support right now, and to
support DDL command to define custom-plan providers.

Planner integration with custom logic to scan a particular relation is
enough simple, unlike various join cases. It's almost similar to what
built-in logic are doing now - custom-plan provider adds a path node
with its cost estimation if it can offer alternative way to scan referenced
relation. (in case of no idea, it does not need to add any paths)

A new DDL syntax I'd like to propose is below:

CREATE CUSTOM PLAN <name> FOR <class> PROVIDER <function_name>;

<name> is as literal, put a unique identifier.
<class> is workload type to be offered by this custom-plan provider.
"scan" is the only option right now, that means base relation scan.
<function_name> is also as literal; it shall perform custom-plan provider.

A custom-plan provider function is assumed to take an argument of
"internal" type to deliver a set of planner information that is needed to
construct custom-plan pathnode.
In case of "scan" class, pointer towards an customScanArg object
shall be delivered on invocation of custom-plan provider.

typedef struct {
uint32 custom_class;
PlannerInfo *root;
RelOptInfo *baserel;
RangeTblEntry *rte;
} customScanArg;

In case when the custom-plan provider function being invoked thought
it can offer an alternative scan path on the relation of "baserel", things
to do is (1) construct a CustomPath (or its inherited data type) object
with a table of callback function pointers (2) put its own cost estimation,
and (3) call add_path() to register this path as an alternative one.

Once the custom-path was chosen by query planner, its CreateCustomPlan
callback is called to populate CustomPlan node based on the pathnode.
It also has a table of callback function pointers to handle various planner's
job in setrefs.c and so on.

Similarly, its CreateCustomPlanState callback is called to populate
CustomPlanState node based on the plannode. It also has a table of
callback function pointers to handle various executor's job during quey
execution.

Most of callback designs are not changed from the prior proposition in
v9.4 development cycle, however, here is a few changes.

* CustomPlan became to inherit Scan, and CustomPlanState became to
inherit ScanState. Because some useful routines to implement scan-
logic, like ExecScan, expects state-node has ScanState as its base
type, it's more kindness for extension side. (I'd like to avoid each
extension reinvent ExecScan by copy & paste!)
I'm not sure whether it should be a union of Join in the future, however,
it is a reasonable choice to have compatible layout with Scan/ScanState
to implement alternative "scan" logic.

* Exporting static functions - I still don't have a graceful answer here.
However, it is quite natural that extensions to follow up interface updates
on the future version up of PostgreSQL.
Probably, it shall become clear what class of functions shall be
exported and what class of functions shall be re-implemented within
extension side in the later discussion.
Right now, I exported minimum ones that are needed to implement
alternative scan method - contrib/ctidscan module.

Items to be discussed later:
* planner integration for relations join - probably, we may define new
custom-plan classes as alternative of hash-join, merge-join and
nest-loop. If core can know this custom-plan is alternative of hash-
join, we can utilize core code to check legality of join.
* generic key-value style options in custom-plan definition - Hanada
san proposed me off-list - like foreign data wrapper. It may enable
to configure multiple behavior on a binary.
* ownership and access control of custom-plan. right now, only
superuser can create/drop custom-plan provider definition, thus,
it has no explicit ownership and access control. It seems to me
a reasonable assumption, however, we may have a usecase that
needs custom-plan by unprivileged users.

Thanks,

2014-05-12 10:09 GMT+09:00 Kouhei Kaigai <kaigai@ak.jp.nec.com>:

On 8 May 2014 22:55, Tom Lane <tgl@sss.pgh.pa.us> wrote:

We're past the prototyping stage and into productionising what we
know works, AFAIK. If that point is not clear, then we need to
discuss that first.

OK, I'll bite: what here do we know works? Not a damn thing AFAICS;
it's all speculation that certain hooks might be useful, and
speculation that's not supported by a lot of evidence. If you think
this isn't prototyping, I wonder what you think *is* prototyping.

My research contacts advise me of this recent work
http://www.ntu.edu.sg/home/bshe/hashjoinonapu_vldb13.pdf
and also that they expect a prototype to be ready by October, which I have
been told will be open source.

So there are at least two groups looking at this as a serious option for
Postgres (not including the above paper's authors).

That isn't *now*, but it is at least a time scale that fits with acting
on this in the next release, if we can separate out the various ideas and
agree we wish to proceed.

I'll submerge again...

Through the discussion last week, our minimum consensus are:
1. Deregulated enhancement of FDW is not a way to go
2. Custom-path that can replace built-in scan makes sense as a first step
towards the future enhancement. Its planner integration is enough simple
to do.
3. Custom-path that can replace built-in join takes investigation how to
integrate existing planner structure, to avoid (3a) reinvention of
whole of join handling in extension side, and (3b) unnecessary extension
calls towards the case obviously unsupported.

So, I'd like to start the (2) portion towards the upcoming 1st commit-fest
on the v9.5 development cycle. Also, we will be able to have discussion
for the (3) portion concurrently, probably, towards 2nd commit-fest.

Unfortunately, I cannot participate PGcon/Ottawa this year. Please share
us the face-to-face discussion here.

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

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

--
Shigeru HANADA

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

--
Shigeru HANADA

--
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: Shigeru Hanada (#4)

Hanada-san,

Thanks for your dedicated reviewing.

It's a very long message. So, let me summarize the things
I shall do in the next patch.

* fix bug: custom-plan class comparison
* fix up naming convention and syntax
CREATE CUSTOM PLAN PROVIDER, rather than
CREATE CUSTOM PLAN. Prefix shall be "cpp_".
* fix up: definition of get_custom_plan_oid()
* fix up: unexpected white spaces, to be tabs.
* fix up: remove unnecessary forward declarations.
* fix up: revert replace_nestloop_params() to static
* make SetCustomPlanRef an optional callback
* fix up: typos in various points
* add documentation to explain custom-plan interface.

Also, I want committer's opinion about the issues below
* whether set_cheapest() is called for all relkind?
* how argument of add_path handler shall be derivered?

Individual comments are put below:

Kaigai-san,

Sorry for lagged response.

Here are my random thoughts about the patch. I couldn't understand the
patch fully, because some of APIs are not used by ctidscan. If

Custom Scan patch v2 review

* Custom plan class comparison
In backend/optimizer/util/pathnode.c, custclass is compared by bit-and
with 's'. Do you plan to use custclass as bit field? If so, values for
custom plan class should not be a character. Otherwise, custclass should
be compared by == operator.

Sorry, it is a bug that come from the previous design.
I had an idea that allows a custom plan provider to support multiple kind
of exec nodes, however, I concluded it does not make sense so much. (we
can define multiple CPP for each)

* Purpose of GetSpecialCustomVar()
The reason why FinalizeCustomPlan callback is necessary is not clear to
me.
Could you show a case that the API would be useful?

It is needed feature to replace a built-in join by custom scan, however,
it might be unclear on the scan workloads.

Let me explain why join replacement needed. A join node has two input
slot (inner and outer), its expression node including Var node reference
either of slot according to its varno (INNER_VAR or OUTER_VAR).
In case when a CPP replaced a join, it has to generate an equivalent result
but it may not be a best choice to use two input streams.
(Please remind when we construct remote join on postgres_fdw, all the
materialization was done on remote side, thus we had one input stream to
generate local join equivalent view.)
On the other hands, EXPLAIN command has to understand what column is the
source of varnodes in targetlist of custom-node even if it is rewritten
to use just one slot. For example, which label shall be shown in case when
3rd item of targetlist is originally come from 2nd item of inner slot but
all the materialized result is stored into outer slot.
Only CPP can track its relationship between the original and the newer one.
This interface provides a way to solve a varnode that actually references.

* Purpose of FinalizeCustomPlan()
The reason why FinalizeCustomPlan callback is necessary is not clear to
me, because ctidscan just calls finalize_primnode() and
bms_add_members() with given information. Could you show a case that the
API would be useful?

The main purpose of this callback gives an extension chance to apply
finalize_primenode() if custom-node hold expression tree on its private
fields.
In case when CPP picked up a part of clauses to run its own way, it shall
be attached on neither plan->targetlist nor plan->qual, only CPP knows
where does it attached. So, these orphan expression nodes have to be
treated by CPP.

* Is it ok to call set_cheapest() for all relkind?
Now set_cheapest() is called not for only relation and foreign table but
also custom plan, and other relations such as subquery, function, and value.
Calling call_custom_scan_provider() and set_cheapest() in the case of
RTE_RELATION seems similar to the old construct, how do you think about
this?

I don't think we may be actually able to have some useful custom scan logic
on these special relation forms, however, I also didn't have a special reason
why custom-plan does not need to support these special relations.
I'd like to see committer's opinion here.

* Is it hard to get rid of CopyCustomPlan()?
Copying ForeignScan node doesn't need per-FDW copy function by limiting
fdw_private to have only copy-able objects. Can't we use the same way for
CustomPlan? Letting authors call NodeSetTag or
copyObject() sounds uncomfortable to me.

This would be able to apply to TextOutCustomPlan() and TextOutCustomPath()
too.

FDW-like design was the original one, but the latest design was suggestion
by Tom on the v9.4 development cycle, because some data types are not
complianced to copyObject; like Bitmapset.

* MultiExec support is appropriate for the first version?
The cases need MultiExec seems little complex for the first version of custom
scan. What kind of plan do you image for this feature?

It is definitely necessary to exchange multiple rows with custom-format with
upper level if both of nodes are managed by same CPP.
I plan to use this interface for bulk-loading that makes much faster data
loading to GPUs.

* Does SupportBackwardScan() have enough information?
Other scans check target list with TargetListSupportsBackwardScan().
Isn't it necessary to check it for CustomPlan too in
ExecSupportsBackwardScan()?

It derivers CustomPlan node itself that includes Plan node.
If CPP thought it is necessary, it can run equivalent checks here.

* Place to call custom plan provider
Is it necessary to call provider when relkind != RELKIND_RELATION? If yes,
isn't it necessary to call for append relation?

I know that we concentrate to replacing scan in the initial version, so
it would not be a serious problem, but it would be good to consider extensible
design.

Regarding of the child relation scan, set_append_rel_pathlist() calls
set_rel_pathlist() that is entry point of custom-scan paths.
If you mention about alternative-path of Append node, yes, it is not
a feature being supported in the first commit.

* Custom Plan Provider is "addpath"?
Passing addpath handler as only one attribute of CUSTOM PLAN PROVIDER seems
little odd.
Using handler like FDW makes the design too complex and/or messy?

This design allows to pass a set of information needed according to the
workload; like join not only scan. If we need to extend customXXXXArg in
the future, all we need to extend is data structure definition, not
function prototype itself.
Anyway, I'd like to make a decision for this on committer review stage.

* superclass of CustomPlanState
CustomPlanState derives ScanState, instead of deriving PlanState directly.
I worry the case of non-heap-scan custom plan, but it might be ok to postpone
consideration about that at the first cut.

We have some useful routines to implement custom-scan logic, but they takes
ScanState argument, like ExecScan().
Even though we can copy it and paste to extension code, it is not a good manner.
It takes three pointer variables in addition to PlanState. If CPP does not
take care about regular heap scan, keep them unused. It is quite helpful if
CPP implements some original logic on top of existing heap scan.

* Naming and granularity of objects related to custom plan I'm not sure
the current naming is appropriate, especially difference between "custom
plan" and "provider" and "handler". In the context of CREATE CUSTOM PLAN
statement, what the term "custom plan" means? My impression is that "custom
plan" is an alternative plan type, e.g.
ctidscan or pg_strom_scan. Then what the term "provider" means? My
impression for that is extension, such as ctidscan and pg_strom. The
grammar allows users to pass function via PROVIDER clause of CREATE CUSTOM
SCAN, so the function would be the provider of the custom plan created by
the statement.

Hmm... What you want to say is, CREATE X statement is expected to create X.
On the other hand, "custom-plan" is actually created by custom-plan provider,
not this DDL statement. The DDL statement defined custom-plan "provider".
I also think the suggestion is reasonable.

How about the statement below instead?

CREATE CUSTOM PLAN PROVIDER cpp_name FOR cpp_kind HANDLER cpp_function;
cpp_kind := SCAN (other types shall be supported later)

* enable_customscan
GUC parameter enable_customscan would be useful for users who want to
disable custom plan feature temporarily. In the case of pg_strom, using
GPU for limited sessions for analytic or batch applications seems handy.

It should be done by extension side individually.
Please imagine a user who install custom-GPU-scan, custom-matview-redirect
and custom-cache-only-scan. Purpose of each CPP are quite individually,
so I don't think enable_customscan makes sense.

* Adding pg_custom_plan catalog
Using "cust" as prefix for pg_custom_plan causes ambiguousness which makes
it difficult to choose catalog prefix for a feature named "Custom Foo" in
future. How about using "cusp" (CUStom Plan)?

Or is it better to use pg_custom_plan_provider as catalog relation name,
as the document says that "CREATE CUSTOM PLAN defines custom plan provider".
Then prefix could be "cpp" (Custom Plan Provider).
This seems to match the wording used for pg_foreign_data_wrapper.

My preference "cpp" as a shorten of custom plan provider.

* CREATE CUSTOM PLAN statement
This is just a question: We need to emit CREATE CUSTOM PLAN if we want
to use I wonder how it is extended when supporting join as custom class.

In case of join, I'll extend the syntax as follows:

CREATE CUSTOM PLAN cppname
FOR [HASH JOIN|MERGE JOIN|NEST LOOP]
PROVIDER provider_func;

Like customScanArg, we will define an argument type for each join methods
then provider_func shall be called with this argument.
I think it is well flexible and extendable approach.

* New operators about TID comparison
IMO this portion should be a separated patch, because it adds OID definition
of existing operators such as tidgt and tidle. Is there any (explicit or
implicit) rule about defining macro for oid of an operator?

I don't know the general rules to define static OID definition.
Probably, these are added on demand.

* Prototype of get_custom_plan_oid()
custname (or cppname if use the rule I proposed above) seems appropriate
as the parameter name of get_custom_plan_oid() because similar funcitons
use catalog column names in such case.

I'll rename it as follows:

extern Oid get_custom_plan_provider_oid(const char *cpp_name, bool missing_ok);

* Coding conventions
Some lines are indented with white space. Future pgindent run will fix
this issue?

It's my oversight, to be fixed.

* Unnecessary struct forward declaration Forward declarations of
CustomPathMethods, Plan, and CustomPlan in includes/nodes/relation.h seem
unncecessary. Other headers might have same issue.

I'll check it. I had try & error during the development. It might leave
a dead code here.

* Unnecessary externing of replace_nestloop_params()
replace_nestloop_params() is extern-ed but it's never called outside
createplan.c.

Indeed, it's not needed until we support custom join logic.

* Externing fix_scan_expr()
If it's necessary for all custom plan providers to call fix_scan_expr (via
fix_scan_list macro), isn't it able to do it in set_plan_refs() before
calling SetCustomPlanRef()?

One alternative idea is:
if scanrelid of custom-plan is valid (scanrelid > 0) and custom-node
has no private expression tree to be fixed up, CPP can have no
SetCustomPlanRef callback. In this case, core backend applies
fix_scan_list on the targetlist and qual, then adjust scanrelid.

It was what I did in the previous revision, that was concerned by Tom
because it assumes too much things to the custom-node. (It is useful
to only custom "scan" node)

* What does T_CustomPlanMarkPos mean?
It's not clear to me when CustomPlanMarkPos works. Is it for a custom plan
provider which supports marking position and rewind to the position, and
ctidscan just lacks capability to do that, so it is not used anywhere?

Its previous design had a flag whether it allows backward scan, in the body
of CustomPlan structure. However, it makes a problem on ExecSupportsMarkRestore()
that takes only node-tag to determine whether the supplied node support
backward scan or not.
Once I tried to change ExecSupportsMarkRestore() to accept node body, then
Tom suggested to use a separated node tag instead.

* Unnecessary changes in allpaths.c
some comment about Subquery and CTE are changed (perhaps) accidentally.

No, it is intentional because set_cheapest() was consolidated.

* Typos
* planenr -> planner, implements -> implement in create_custom_plan.sgml
* CustomScan in nodeCustom.h should be CustomPlan?
* delivered -> derived, in src/backend/optimizer/util/pathnode.c

OK, I'll fix them.

* Document "Writing Custom Plan Provider" is not provided Custom Plan
Provider author would (and I DO!) hope documents about writing a custom
plan provider.

A documentation like fdwhandler.sgml, isn't it?
OK, I'll make it up.

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

2014-06-17 23:12 GMT+09:00 Kohei KaiGai <kaigai@kaigai.gr.jp>:

Hanada-san,

Thanks for your checks. I oversight the points when I submit the patch,

sorry.

The attached one is revised one on documentation stuff and

contrib/Makefile.

Thanks,

2014-06-16 17:29 GMT+09:00 Shigeru Hanada <shigeru.hanada@gmail.com>:

Kaigai-san,

I've just applied v1 patch, and tried build and install, but I found

two issues:

1) The contrib/ctidscan is not automatically built/installed because
it's not described in contrib/Makefile. Is this expected behavior?
2) I got an error message below when building document.

$ cd doc/src/sgml
$ make
openjade -wall -wno-unused-param -wno-empty -wfully-tagged -D . -D .
-d stylesheet.dsl -t sgml -i output-html -V html-index postgres.sgml
openjade:catalogs.sgml:2525:45:X: reference to non-existent ID
"SQL-CREATECUSTOMPLAN"
make: *** [HTML.index] Error 1
make: *** Deleting file `HTML.index'

I'll review another part of the patch, including the design.

2014-06-14 10:59 GMT+09:00 Kohei KaiGai <kaigai@kaigai.gr.jp>:

According to the discussion upthread, I revised the custom-plan
patch to focus on regular relation scan but no join support right
now, and to support DDL command to define custom-plan providers.

Planner integration with custom logic to scan a particular relation
is enough simple, unlike various join cases. It's almost similar to
what built-in logic are doing now - custom-plan provider adds a path
node with its cost estimation if it can offer alternative way to
scan referenced relation. (in case of no idea, it does not need to
add any paths)

A new DDL syntax I'd like to propose is below:

CREATE CUSTOM PLAN <name> FOR <class> PROVIDER <function_name>;

<name> is as literal, put a unique identifier.
<class> is workload type to be offered by this custom-plan provider.
"scan" is the only option right now, that means base relation scan.
<function_name> is also as literal; it shall perform custom-plan

provider.

A custom-plan provider function is assumed to take an argument of
"internal" type to deliver a set of planner information that is
needed to construct custom-plan pathnode.
In case of "scan" class, pointer towards an customScanArg object
shall be delivered on invocation of custom-plan provider.

typedef struct {
uint32 custom_class;
PlannerInfo *root;
RelOptInfo *baserel;
RangeTblEntry *rte;
} customScanArg;

In case when the custom-plan provider function being invoked thought
it can offer an alternative scan path on the relation of "baserel",
things to do is (1) construct a CustomPath (or its inherited data
type) object with a table of callback function pointers (2) put its
own cost estimation, and (3) call add_path() to register this path as

an alternative one.

Once the custom-path was chosen by query planner, its
CreateCustomPlan callback is called to populate CustomPlan node based

on the pathnode.

It also has a table of callback function pointers to handle various
planner's job in setrefs.c and so on.

Similarly, its CreateCustomPlanState callback is called to populate
CustomPlanState node based on the plannode. It also has a table of
callback function pointers to handle various executor's job during
quey execution.

Most of callback designs are not changed from the prior proposition
in
v9.4 development cycle, however, here is a few changes.

* CustomPlan became to inherit Scan, and CustomPlanState became to
inherit ScanState. Because some useful routines to implement scan-
logic, like ExecScan, expects state-node has ScanState as its base
type, it's more kindness for extension side. (I'd like to avoid each
extension reinvent ExecScan by copy & paste!)
I'm not sure whether it should be a union of Join in the future,

however,

it is a reasonable choice to have compatible layout with

Scan/ScanState

to implement alternative "scan" logic.

* Exporting static functions - I still don't have a graceful answer

here.

However, it is quite natural that extensions to follow up interface

updates

on the future version up of PostgreSQL.
Probably, it shall become clear what class of functions shall be
exported and what class of functions shall be re-implemented within
extension side in the later discussion.
Right now, I exported minimum ones that are needed to implement
alternative scan method - contrib/ctidscan module.

Items to be discussed later:
* planner integration for relations join - probably, we may define new
custom-plan classes as alternative of hash-join, merge-join and
nest-loop. If core can know this custom-plan is alternative of hash-
join, we can utilize core code to check legality of join.
* generic key-value style options in custom-plan definition - Hanada
san proposed me off-list - like foreign data wrapper. It may enable
to configure multiple behavior on a binary.
* ownership and access control of custom-plan. right now, only
superuser can create/drop custom-plan provider definition, thus,
it has no explicit ownership and access control. It seems to me
a reasonable assumption, however, we may have a usecase that
needs custom-plan by unprivileged users.

Thanks,

2014-05-12 10:09 GMT+09:00 Kouhei Kaigai <kaigai@ak.jp.nec.com>:

On 8 May 2014 22:55, Tom Lane <tgl@sss.pgh.pa.us> wrote:

We're past the prototyping stage and into productionising what
we know works, AFAIK. If that point is not clear, then we need
to discuss that first.

OK, I'll bite: what here do we know works? Not a damn thing
AFAICS; it's all speculation that certain hooks might be useful,
and speculation that's not supported by a lot of evidence. If
you think this isn't prototyping, I wonder what you think *is*

prototyping.

My research contacts advise me of this recent work
http://www.ntu.edu.sg/home/bshe/hashjoinonapu_vldb13.pdf
and also that they expect a prototype to be ready by October,
which I have been told will be open source.

So there are at least two groups looking at this as a serious
option for Postgres (not including the above paper's authors).

That isn't *now*, but it is at least a time scale that fits with
acting on this in the next release, if we can separate out the
various ideas and agree we wish to proceed.

I'll submerge again...

Through the discussion last week, our minimum consensus are:
1. Deregulated enhancement of FDW is not a way to go 2. Custom-path
that can replace built-in scan makes sense as a first step
towards the future enhancement. Its planner integration is enough

simple

to do.
3. Custom-path that can replace built-in join takes investigation how

to

integrate existing planner structure, to avoid (3a) reinvention

of

whole of join handling in extension side, and (3b) unnecessary

extension

calls towards the case obviously unsupported.

So, I'd like to start the (2) portion towards the upcoming 1st
commit-fest on the v9.5 development cycle. Also, we will be able to
have discussion for the (3) portion concurrently, probably, towards

2nd commit-fest.

Unfortunately, I cannot participate PGcon/Ottawa this year. Please
share us the face-to-face discussion here.

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

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

--
Shigeru HANADA

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

--
Shigeru HANADA

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

#6Kouhei Kaigai
kaigai@ak.jp.nec.com
In reply to: Kouhei Kaigai (#5)
1 attachment(s)

Hanada-san,

The attached patch is revised one.
Updates from the previous version are below:

* System catalog name was changed to pg_custom_plan_provider;
that reflects role of the object being defined.
* Also, prefix of its variable names are changed to "cpp"; that
means custom-plan-provider.
* Syntax also reflects what the command does more. New syntax to
define custom plan provider is:
CREATE CUSTOM PLAN PROVIDER <cpp_name>
FOR <cpp_class> HANDLER <cpp_function>;
* Add custom-plan.sgml to introduce interface functions defined
for path/plan/exec methods.
* FinalizeCustomPlan() callback was simplified to support scan
(and join in the future) at the starting point. As long as
scan/join requirement, no need to control paramids bitmap in
arbitrary way.
* Unnecessary forward declaration in relation.h and plannode.h
were removed, but a few structures still needs to have
forward declarations.
* Fix typos being pointed out.

I'd like to see committer's suggestion regarding to the design
issues below:

* whether set_cheapest() is called for all relkind?
->according to the discussion in v9.4 cycle, I consolidated
set_cheapest() in allpaths.c to set_rel_pathlist().
Hanada-san wonder whether it is necessary to have custom-
plan on none base relations; like sub-query or values-scan.
I don't have reason why not to run custom-plan on these
non usual relations.

* how argument of add_path handler shall be derivered?
-> custom-plan handler function takes an argument with
internal data type; that is a pointer of customScanArg
if custom-plan class is "scan". (It shall be
customHashJoinArg if "hash join" for example).

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

Show quoted text

-----Original Message-----
From: Kaigai Kouhei(海外 浩平)
Sent: Friday, July 04, 2014 1:23 PM
To: 'Shigeru Hanada'; Kohei KaiGai
Cc: Simon Riggs; Tom Lane; Stephen Frost; Robert Haas; Andres Freund;
PgHacker; Jim Mlodgenski; Peter Eisentraut
Subject: Re: [HACKERS] [v9.5] Custom Plan API

Hanada-san,

Thanks for your dedicated reviewing.

It's a very long message. So, let me summarize the things I shall do in
the next patch.

* fix bug: custom-plan class comparison
* fix up naming convention and syntax
CREATE CUSTOM PLAN PROVIDER, rather than
CREATE CUSTOM PLAN. Prefix shall be "cpp_".
* fix up: definition of get_custom_plan_oid()
* fix up: unexpected white spaces, to be tabs.
* fix up: remove unnecessary forward declarations.
* fix up: revert replace_nestloop_params() to static
* make SetCustomPlanRef an optional callback
* fix up: typos in various points
* add documentation to explain custom-plan interface.

Also, I want committer's opinion about the issues below
* whether set_cheapest() is called for all relkind?
* how argument of add_path handler shall be derivered?

Individual comments are put below:

Kaigai-san,

Sorry for lagged response.

Here are my random thoughts about the patch. I couldn't understand
the patch fully, because some of APIs are not used by ctidscan. If

Custom Scan patch v2 review

* Custom plan class comparison
In backend/optimizer/util/pathnode.c, custclass is compared by bit-and
with 's'. Do you plan to use custclass as bit field? If so, values
for custom plan class should not be a character. Otherwise, custclass
should be compared by == operator.

Sorry, it is a bug that come from the previous design.
I had an idea that allows a custom plan provider to support multiple kind
of exec nodes, however, I concluded it does not make sense so much. (we
can define multiple CPP for each)

* Purpose of GetSpecialCustomVar()
The reason why FinalizeCustomPlan callback is necessary is not clear
to me.
Could you show a case that the API would be useful?

It is needed feature to replace a built-in join by custom scan, however,
it might be unclear on the scan workloads.

Let me explain why join replacement needed. A join node has two input slot
(inner and outer), its expression node including Var node reference either
of slot according to its varno (INNER_VAR or OUTER_VAR).
In case when a CPP replaced a join, it has to generate an equivalent result
but it may not be a best choice to use two input streams.
(Please remind when we construct remote join on postgres_fdw, all the
materialization was done on remote side, thus we had one input stream to
generate local join equivalent view.) On the other hands, EXPLAIN command
has to understand what column is the source of varnodes in targetlist of
custom-node even if it is rewritten to use just one slot. For example, which
label shall be shown in case when 3rd item of targetlist is originally come
from 2nd item of inner slot but all the materialized result is stored into
outer slot.
Only CPP can track its relationship between the original and the newer one.
This interface provides a way to solve a varnode that actually references.

* Purpose of FinalizeCustomPlan()
The reason why FinalizeCustomPlan callback is necessary is not clear
to me, because ctidscan just calls finalize_primnode() and
bms_add_members() with given information. Could you show a case that
the API would be useful?

The main purpose of this callback gives an extension chance to apply
finalize_primenode() if custom-node hold expression tree on its private
fields.
In case when CPP picked up a part of clauses to run its own way, it shall
be attached on neither plan->targetlist nor plan->qual, only CPP knows where
does it attached. So, these orphan expression nodes have to be treated by
CPP.

* Is it ok to call set_cheapest() for all relkind?
Now set_cheapest() is called not for only relation and foreign table
but also custom plan, and other relations such as subquery, function,

and value.

Calling call_custom_scan_provider() and set_cheapest() in the case of
RTE_RELATION seems similar to the old construct, how do you think
about this?

I don't think we may be actually able to have some useful custom scan logic
on these special relation forms, however, I also didn't have a special reason
why custom-plan does not need to support these special relations.
I'd like to see committer's opinion here.

* Is it hard to get rid of CopyCustomPlan()?
Copying ForeignScan node doesn't need per-FDW copy function by
limiting fdw_private to have only copy-able objects. Can't we use the
same way for CustomPlan? Letting authors call NodeSetTag or
copyObject() sounds uncomfortable to me.

This would be able to apply to TextOutCustomPlan() and
TextOutCustomPath() too.

FDW-like design was the original one, but the latest design was suggestion
by Tom on the v9.4 development cycle, because some data types are not
complianced to copyObject; like Bitmapset.

* MultiExec support is appropriate for the first version?
The cases need MultiExec seems little complex for the first version of
custom scan. What kind of plan do you image for this feature?

It is definitely necessary to exchange multiple rows with custom-format
with upper level if both of nodes are managed by same CPP.
I plan to use this interface for bulk-loading that makes much faster data
loading to GPUs.

* Does SupportBackwardScan() have enough information?
Other scans check target list with TargetListSupportsBackwardScan().
Isn't it necessary to check it for CustomPlan too in
ExecSupportsBackwardScan()?

It derivers CustomPlan node itself that includes Plan node.
If CPP thought it is necessary, it can run equivalent checks here.

* Place to call custom plan provider
Is it necessary to call provider when relkind != RELKIND_RELATION? If
yes, isn't it necessary to call for append relation?

I know that we concentrate to replacing scan in the initial version,
so it would not be a serious problem, but it would be good to consider
extensible design.

Regarding of the child relation scan, set_append_rel_pathlist() calls
set_rel_pathlist() that is entry point of custom-scan paths.
If you mention about alternative-path of Append node, yes, it is not a
feature being supported in the first commit.

* Custom Plan Provider is "addpath"?
Passing addpath handler as only one attribute of CUSTOM PLAN PROVIDER
seems little odd.
Using handler like FDW makes the design too complex and/or messy?

This design allows to pass a set of information needed according to the
workload; like join not only scan. If we need to extend customXXXXArg in
the future, all we need to extend is data structure definition, not function
prototype itself.
Anyway, I'd like to make a decision for this on committer review stage.

* superclass of CustomPlanState
CustomPlanState derives ScanState, instead of deriving PlanState

directly.

I worry the case of non-heap-scan custom plan, but it might be ok to
postpone consideration about that at the first cut.

We have some useful routines to implement custom-scan logic, but they takes
ScanState argument, like ExecScan().
Even though we can copy it and paste to extension code, it is not a good
manner.
It takes three pointer variables in addition to PlanState. If CPP does not
take care about regular heap scan, keep them unused. It is quite helpful
if CPP implements some original logic on top of existing heap scan.

* Naming and granularity of objects related to custom plan I'm not
sure the current naming is appropriate, especially difference between
"custom plan" and "provider" and "handler". In the context of CREATE
CUSTOM PLAN statement, what the term "custom plan" means? My
impression is that "custom plan" is an alternative plan type, e.g.
ctidscan or pg_strom_scan. Then what the term "provider" means? My
impression for that is extension, such as ctidscan and pg_strom. The
grammar allows users to pass function via PROVIDER clause of CREATE
CUSTOM SCAN, so the function would be the provider of the custom plan
created by the statement.

Hmm... What you want to say is, CREATE X statement is expected to create
X.
On the other hand, "custom-plan" is actually created by custom-plan provider,
not this DDL statement. The DDL statement defined custom-plan "provider".
I also think the suggestion is reasonable.

How about the statement below instead?

CREATE CUSTOM PLAN PROVIDER cpp_name FOR cpp_kind HANDLER cpp_function;
cpp_kind := SCAN (other types shall be supported later)

* enable_customscan
GUC parameter enable_customscan would be useful for users who want to
disable custom plan feature temporarily. In the case of pg_strom,
using GPU for limited sessions for analytic or batch applications seems

handy.

It should be done by extension side individually.
Please imagine a user who install custom-GPU-scan, custom-matview-redirect
and custom-cache-only-scan. Purpose of each CPP are quite individually,
so I don't think enable_customscan makes sense.

* Adding pg_custom_plan catalog
Using "cust" as prefix for pg_custom_plan causes ambiguousness which
makes it difficult to choose catalog prefix for a feature named
"Custom Foo" in future. How about using "cusp" (CUStom Plan)?

Or is it better to use pg_custom_plan_provider as catalog relation
name, as the document says that "CREATE CUSTOM PLAN defines custom plan

provider".

Then prefix could be "cpp" (Custom Plan Provider).
This seems to match the wording used for pg_foreign_data_wrapper.

My preference "cpp" as a shorten of custom plan provider.

* CREATE CUSTOM PLAN statement
This is just a question: We need to emit CREATE CUSTOM PLAN if we
want to use I wonder how it is extended when supporting join as custom

class.

In case of join, I'll extend the syntax as follows:

CREATE CUSTOM PLAN cppname
FOR [HASH JOIN|MERGE JOIN|NEST LOOP]
PROVIDER provider_func;

Like customScanArg, we will define an argument type for each join methods
then provider_func shall be called with this argument.
I think it is well flexible and extendable approach.

* New operators about TID comparison
IMO this portion should be a separated patch, because it adds OID
definition of existing operators such as tidgt and tidle. Is there
any (explicit or
implicit) rule about defining macro for oid of an operator?

I don't know the general rules to define static OID definition.
Probably, these are added on demand.

* Prototype of get_custom_plan_oid()
custname (or cppname if use the rule I proposed above) seems
appropriate as the parameter name of get_custom_plan_oid() because
similar funcitons use catalog column names in such case.

I'll rename it as follows:

extern Oid get_custom_plan_provider_oid(const char *cpp_name, bool
missing_ok);

* Coding conventions
Some lines are indented with white space. Future pgindent run will
fix this issue?

It's my oversight, to be fixed.

* Unnecessary struct forward declaration Forward declarations of
CustomPathMethods, Plan, and CustomPlan in includes/nodes/relation.h
seem unncecessary. Other headers might have same issue.

I'll check it. I had try & error during the development. It might leave
a dead code here.

* Unnecessary externing of replace_nestloop_params()
replace_nestloop_params() is extern-ed but it's never called outside
createplan.c.

Indeed, it's not needed until we support custom join logic.

* Externing fix_scan_expr()
If it's necessary for all custom plan providers to call fix_scan_expr
(via fix_scan_list macro), isn't it able to do it in set_plan_refs()
before calling SetCustomPlanRef()?

One alternative idea is:
if scanrelid of custom-plan is valid (scanrelid > 0) and custom-node
has no private expression tree to be fixed up, CPP can have no
SetCustomPlanRef callback. In this case, core backend applies
fix_scan_list on the targetlist and qual, then adjust scanrelid.

It was what I did in the previous revision, that was concerned by Tom because
it assumes too much things to the custom-node. (It is useful to only custom
"scan" node)

* What does T_CustomPlanMarkPos mean?
It's not clear to me when CustomPlanMarkPos works. Is it for a custom
plan provider which supports marking position and rewind to the
position, and ctidscan just lacks capability to do that, so it is not

used anywhere?

Its previous design had a flag whether it allows backward scan, in the body
of CustomPlan structure. However, it makes a problem on
ExecSupportsMarkRestore() that takes only node-tag to determine whether
the supplied node support backward scan or not.
Once I tried to change ExecSupportsMarkRestore() to accept node body, then
Tom suggested to use a separated node tag instead.

* Unnecessary changes in allpaths.c
some comment about Subquery and CTE are changed (perhaps) accidentally.

No, it is intentional because set_cheapest() was consolidated.

* Typos
* planenr -> planner, implements -> implement in

create_custom_plan.sgml

* CustomScan in nodeCustom.h should be CustomPlan?
* delivered -> derived, in src/backend/optimizer/util/pathnode.c

OK, I'll fix them.

* Document "Writing Custom Plan Provider" is not provided Custom Plan
Provider author would (and I DO!) hope documents about writing a
custom plan provider.

A documentation like fdwhandler.sgml, isn't it?
OK, I'll make it up.

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

2014-06-17 23:12 GMT+09:00 Kohei KaiGai <kaigai@kaigai.gr.jp>:

Hanada-san,

Thanks for your checks. I oversight the points when I submit the
patch,

sorry.

The attached one is revised one on documentation stuff and

contrib/Makefile.

Thanks,

2014-06-16 17:29 GMT+09:00 Shigeru Hanada <shigeru.hanada@gmail.com>:

Kaigai-san,

I've just applied v1 patch, and tried build and install, but I
found

two issues:

1) The contrib/ctidscan is not automatically built/installed
because it's not described in contrib/Makefile. Is this expected

behavior?

2) I got an error message below when building document.

$ cd doc/src/sgml
$ make
openjade -wall -wno-unused-param -wno-empty -wfully-tagged -D . -D .
-d stylesheet.dsl -t sgml -i output-html -V html-index
postgres.sgml
openjade:catalogs.sgml:2525:45:X: reference to non-existent ID
"SQL-CREATECUSTOMPLAN"
make: *** [HTML.index] Error 1
make: *** Deleting file `HTML.index'

I'll review another part of the patch, including the design.

2014-06-14 10:59 GMT+09:00 Kohei KaiGai <kaigai@kaigai.gr.jp>:

According to the discussion upthread, I revised the custom-plan
patch to focus on regular relation scan but no join support right
now, and to support DDL command to define custom-plan providers.

Planner integration with custom logic to scan a particular
relation is enough simple, unlike various join cases. It's almost
similar to what built-in logic are doing now - custom-plan
provider adds a path node with its cost estimation if it can offer
alternative way to scan referenced relation. (in case of no idea,
it does not need to add any paths)

A new DDL syntax I'd like to propose is below:

CREATE CUSTOM PLAN <name> FOR <class> PROVIDER <function_name>;

<name> is as literal, put a unique identifier.
<class> is workload type to be offered by this custom-plan provider.
"scan" is the only option right now, that means base relation scan.
<function_name> is also as literal; it shall perform custom-plan

provider.

A custom-plan provider function is assumed to take an argument of
"internal" type to deliver a set of planner information that is
needed to construct custom-plan pathnode.
In case of "scan" class, pointer towards an customScanArg object
shall be delivered on invocation of custom-plan provider.

typedef struct {
uint32 custom_class;
PlannerInfo *root;
RelOptInfo *baserel;
RangeTblEntry *rte;
} customScanArg;

In case when the custom-plan provider function being invoked
thought it can offer an alternative scan path on the relation of
"baserel", things to do is (1) construct a CustomPath (or its
inherited data
type) object with a table of callback function pointers (2) put
its own cost estimation, and (3) call add_path() to register this
path as

an alternative one.

Once the custom-path was chosen by query planner, its
CreateCustomPlan callback is called to populate CustomPlan node
based

on the pathnode.

It also has a table of callback function pointers to handle
various planner's job in setrefs.c and so on.

Similarly, its CreateCustomPlanState callback is called to
populate CustomPlanState node based on the plannode. It also has a
table of callback function pointers to handle various executor's
job during quey execution.

Most of callback designs are not changed from the prior
proposition in
v9.4 development cycle, however, here is a few changes.

* CustomPlan became to inherit Scan, and CustomPlanState became to
inherit ScanState. Because some useful routines to implement scan-
logic, like ExecScan, expects state-node has ScanState as its base
type, it's more kindness for extension side. (I'd like to avoid

each

extension reinvent ExecScan by copy & paste!)
I'm not sure whether it should be a union of Join in the future,

however,

it is a reasonable choice to have compatible layout with

Scan/ScanState

to implement alternative "scan" logic.

* Exporting static functions - I still don't have a graceful
answer

here.

However, it is quite natural that extensions to follow up
interface

updates

on the future version up of PostgreSQL.
Probably, it shall become clear what class of functions shall be
exported and what class of functions shall be re-implemented within
extension side in the later discussion.
Right now, I exported minimum ones that are needed to implement
alternative scan method - contrib/ctidscan module.

Items to be discussed later:
* planner integration for relations join - probably, we may define

new

custom-plan classes as alternative of hash-join, merge-join and
nest-loop. If core can know this custom-plan is alternative of hash-
join, we can utilize core code to check legality of join.
* generic key-value style options in custom-plan definition - Hanada
san proposed me off-list - like foreign data wrapper. It may enable
to configure multiple behavior on a binary.
* ownership and access control of custom-plan. right now, only
superuser can create/drop custom-plan provider definition, thus,
it has no explicit ownership and access control. It seems to me
a reasonable assumption, however, we may have a usecase that
needs custom-plan by unprivileged users.

Thanks,

2014-05-12 10:09 GMT+09:00 Kouhei Kaigai <kaigai@ak.jp.nec.com>:

On 8 May 2014 22:55, Tom Lane <tgl@sss.pgh.pa.us> wrote:

We're past the prototyping stage and into productionising
what we know works, AFAIK. If that point is not clear, then
we need to discuss that first.

OK, I'll bite: what here do we know works? Not a damn thing
AFAICS; it's all speculation that certain hooks might be
useful, and speculation that's not supported by a lot of
evidence. If you think this isn't prototyping, I wonder what
you think *is*

prototyping.

My research contacts advise me of this recent work
http://www.ntu.edu.sg/home/bshe/hashjoinonapu_vldb13.pdf
and also that they expect a prototype to be ready by October,
which I have been told will be open source.

So there are at least two groups looking at this as a serious
option for Postgres (not including the above paper's authors).

That isn't *now*, but it is at least a time scale that fits with
acting on this in the next release, if we can separate out the
various ideas and agree we wish to proceed.

I'll submerge again...

Through the discussion last week, our minimum consensus are:
1. Deregulated enhancement of FDW is not a way to go 2.
Custom-path that can replace built-in scan makes sense as a first

step

towards the future enhancement. Its planner integration is
enough

simple

to do.
3. Custom-path that can replace built-in join takes investigation
how

to

integrate existing planner structure, to avoid (3a)
reinvention

of

whole of join handling in extension side, and (3b) unnecessary

extension

calls towards the case obviously unsupported.

So, I'd like to start the (2) portion towards the upcoming 1st
commit-fest on the v9.5 development cycle. Also, we will be able
to have discussion for the (3) portion concurrently, probably,
towards

2nd commit-fest.

Unfortunately, I cannot participate PGcon/Ottawa this year.
Please share us the face-to-face discussion here.

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

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

--
Shigeru HANADA

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

--
Shigeru HANADA

Attachments:

pgsql-v9.5-custom-plan.v3.patchapplication/octet-stream; name=pgsql-v9.5-custom-plan.v3.patchDownload
 contrib/Makefile                                  |   1 +
 contrib/ctidscan/Makefile                         |  19 +
 contrib/ctidscan/ctidscan--1.0.sql                |  12 +
 contrib/ctidscan/ctidscan--unpackaged-1.0.sql     |   0
 contrib/ctidscan/ctidscan.c                       | 952 ++++++++++++++++++++++
 contrib/ctidscan/ctidscan.control                 |   5 +
 contrib/ctidscan/expected/ctidscan.out            | 312 +++++++
 contrib/ctidscan/sql/ctidscan.sql                 |  54 ++
 doc/src/sgml/catalogs.sgml                        |  59 ++
 doc/src/sgml/custom-plan.sgml                     | 431 ++++++++++
 doc/src/sgml/filelist.sgml                        |   1 +
 doc/src/sgml/postgres.sgml                        |   1 +
 doc/src/sgml/ref/allfiles.sgml                    |   2 +
 doc/src/sgml/ref/create_custom_plan_provider.sgml | 139 ++++
 doc/src/sgml/ref/drop_custom_plan_provider.sgml   | 108 +++
 doc/src/sgml/reference.sgml                       |   2 +
 src/backend/catalog/Makefile                      |   2 +-
 src/backend/catalog/dependency.c                  |  11 +-
 src/backend/catalog/objectaddress.c               |  63 ++
 src/backend/commands/Makefile                     |   2 +-
 src/backend/commands/custom_plan.c                | 188 +++++
 src/backend/commands/dropcmds.c                   |   5 +
 src/backend/commands/event_trigger.c              |   2 +
 src/backend/commands/explain.c                    |  40 +
 src/backend/executor/Makefile                     |   2 +-
 src/backend/executor/execAmi.c                    |  25 +
 src/backend/executor/execProcnode.c               |  19 +
 src/backend/executor/nodeCustom.c                 | 147 ++++
 src/backend/nodes/copyfuncs.c                     |  35 +
 src/backend/nodes/equalfuncs.c                    |  14 +
 src/backend/nodes/outfuncs.c                      |  33 +
 src/backend/optimizer/path/allpaths.c             |  30 +-
 src/backend/optimizer/plan/createplan.c           |  98 ++-
 src/backend/optimizer/plan/setrefs.c              |  11 +-
 src/backend/optimizer/plan/subselect.c            |  24 +
 src/backend/optimizer/util/pathnode.c             | 110 +++
 src/backend/parser/gram.y                         |  94 ++-
 src/backend/tcop/utility.c                        |  20 +
 src/backend/utils/adt/ruleutils.c                 |  73 ++
 src/backend/utils/cache/syscache.c                |  23 +
 src/include/catalog/dependency.h                  |   1 +
 src/include/catalog/indexing.h                    |   6 +
 src/include/catalog/pg_custom_plan_provider.h     |  50 ++
 src/include/catalog/pg_operator.h                 |   3 +
 src/include/commands/defrem.h                     |   5 +
 src/include/executor/nodeCustom.h                 |  30 +
 src/include/nodes/execnodes.h                     |  42 +
 src/include/nodes/nodes.h                         |   5 +
 src/include/nodes/parsenodes.h                    |  13 +
 src/include/nodes/plannodes.h                     |  37 +
 src/include/nodes/relation.h                      |  26 +
 src/include/optimizer/pathnode.h                  |  14 +
 src/include/optimizer/planmain.h                  |   2 +
 src/include/parser/kwlist.h                       |   4 +
 src/include/utils/syscache.h                      |   2 +
 src/test/regress/expected/sanity_check.out        |   1 +
 56 files changed, 3374 insertions(+), 36 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..1e476a6
--- /dev/null
+++ b/contrib/ctidscan/Makefile
@@ -0,0 +1,19 @@
+# contrib/ctidscan/Makefile
+
+MODULES = ctidscan
+
+EXTENSION = ctidscan
+DATA = ctidscan--1.0.sql
+
+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--1.0.sql b/contrib/ctidscan/ctidscan--1.0.sql
new file mode 100644
index 0000000..46420a0
--- /dev/null
+++ b/contrib/ctidscan/ctidscan--1.0.sql
@@ -0,0 +1,12 @@
+--
+-- Create ctidscan handler function
+--
+CREATE FUNCTION ctidscanaddpath(internal)
+  RETURNS pg_catalog.void
+  AS 'MODULE_PATHNAME','CtidScanAddPath'
+  LANGUAGE C STRICT;
+
+--
+-- Create a custom-plan provider
+--
+CREATE CUSTOM PLAN PROVIDER ctidscan FOR scan HANDLER ctidscanaddpath;
diff --git a/contrib/ctidscan/ctidscan--unpackaged-1.0.sql b/contrib/ctidscan/ctidscan--unpackaged-1.0.sql
new file mode 100644
index 0000000..e69de29
diff --git a/contrib/ctidscan/ctidscan.c b/contrib/ctidscan/ctidscan.c
new file mode 100644
index 0000000..327a6d2
--- /dev/null
+++ b/contrib/ctidscan/ctidscan.c
@@ -0,0 +1,952 @@
+/*
+ * 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_custom_plan_provider.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 {
+	CustomPlan		cplan;
+	List		   *ctid_quals;
+} CtidScanPlan;
+
+typedef struct {
+	CustomPlanState	cps;
+	List		   *ctid_quals;		/* list of ExprState for inequality ops */
+} CtidScanState;
+
+static CustomPathMethods	ctidscan_path_methods;
+static CustomPlanMethods	ctidscan_plan_methods;
+static CustomExecMethods	ctidscan_exec_methods;
+
+/* function declarations */
+void	_PG_init(void);
+Datum	CtidScanAddPath(PG_FUNCTION_ARGS);
+
+#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 undeterministic untill
+		 * executor run it actually.
+		 */
+		num_pages = Max((baserel->pages + 1) / 2, 1);
+	}
+	ntuples *= ((double) num_pages) / ((double) baserel->pages);
+
+	/*
+	 * The TID qual expressions will be computed once, any other baserestrict
+	 * quals once per 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;
+}
+
+/*
+ * CreateCtidScanPlan - A method of CustomPath; that populate a custom
+ * object being delivered from CustomPlan type, according to the supplied
+ * CustomPath object.
+ */
+static Node *
+CreateCtidScanPlan(PlannerInfo *root, CustomPath *best_path)
+{
+	CtidScanPath   *ctid_path = (CtidScanPath *) best_path;
+	CtidScanPlan   *ctid_scan;
+
+	ctid_scan = palloc0(sizeof(CtidScanPlan));
+	NodeSetTag(ctid_scan, T_CustomPlan);
+	ctid_scan->cplan.methods = &ctidscan_plan_methods;
+	ctid_scan->ctid_quals = ctid_path->ctid_quals;
+
+	return (Node *)&ctid_scan->cplan;
+}
+
+/*
+ * InitCtidScanPlan
+ *
+ * main portion to initialize CustomPlan node.
+ */
+static Plan *
+InitCtidScanPlan(CustomPlan *custom_plan,
+				 PlannerInfo *root, CustomPath *best_path,
+				 List *tlist, List *clauses)
+{
+	CtidScanPlan   *ctid_scan = (CtidScanPlan *)custom_plan;
+	List		   *ctid_quals = ((CtidScanPath *)best_path)->ctid_quals;
+
+	Assert(ctid_scan->cplan.scan.scanrelid > 0);
+
+	/* Set targetlist as is */
+	ctid_scan->cplan.scan.plan.targetlist = tlist;
+	/* Reduce RestrictInfo list to bare expressions */
+	ctid_scan->cplan.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 &ctid_scan->cplan.scan.plan;
+}
+
+/*
+ * 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 CustomPlan; 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,
+				   CustomPlan *custom_plan,
+				   int rtoffset)
+{
+	CtidScanPlan   *ctidscan = (CtidScanPlan *) custom_plan;
+	Scan		   *scan = &ctidscan->cplan.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);
+}
+
+/*
+ * SupportCtidBackwardScan - A method of CustomPlan; that informs the core
+ * backend whether this custom-plan node support backward scan or not.
+ */
+static bool
+SupportCtidBackwardScan(CustomPlan *custom_plan)
+{
+	return true;
+}
+
+/*
+ * FinalizeCtidScanPlan - A method of CustomPlan; that handles callbacks
+ * by finalize_plan().
+ */
+static void
+FinalizeCtidScanPlan(PlannerInfo *root,
+					 CustomPlan *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 CustomPlan; that populate a custom
+ * object being delivered from CustomPlanState type, according to the
+ * supplied CustomPath object.
+ */
+static Node *
+CreateCtidScanState(CustomPlan *custom_plan)
+{
+	CtidScanState  *ctss = palloc0(sizeof(CtidScanState));
+
+	NodeSetTag(ctss, T_CustomPlanState);
+	ctss->cps.methods = &ctidscan_exec_methods;
+
+	return (Node *)&ctss->cps;
+}
+
+/*
+ * TextOutCtidScanPlan - A method of CustomPlan; that generates text
+ * representation of the given object.
+ */
+static void
+TextOutCtidScanPlan(StringInfo str, const CustomPlan *node)
+{
+	CtidScanPlan   *ctid_plan = (CtidScanPlan *) node;
+
+	appendStringInfo(str, " :ctid_quals %s",
+					 nodeToString(ctid_plan->ctid_quals));
+}
+
+/*
+ * CopyCtidScanPlan - A method of CustomPlan; that create a copied object.
+ */
+static CustomPlan *
+CopyCtidScanPlan(const CustomPlan *from)
+{
+	CtidScanPlan   *oldnode = (CtidScanPlan *) from;
+	CtidScanPlan   *newnode = palloc0(sizeof(CtidScanPlan));
+
+	NodeSetTag(newnode, T_CustomPlan);
+	newnode->cplan.methods = oldnode->cplan.methods;
+	newnode->ctid_quals = copyObject(oldnode->ctid_quals);
+
+	return &newnode->cplan;
+}
+
+/*
+ * BeginCtidScan - A method of CustomPlanState; that initializes
+ * the supplied CtidScanState object, at begining of the executor.
+ */
+static void
+BeginCtidScan(CustomPlanState *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->cps.ss.ss_currentScanDesc = NULL;
+}
+
+/*
+ * ReScanCtidScan - A method of CustomPlanState; that rewind the current
+ * seek position.
+ */
+static void
+ReScanCtidScan(CustomPlanState *node)
+{
+	CtidScanState  *ctss = (CtidScanState *)node;
+	HeapScanDesc	scan = ctss->cps.ss.ss_currentScanDesc;
+	EState		   *estate = node->ss.ps.state;
+	ScanDirection	direction = estate->es_direction;
+	Relation		relation = ctss->cps.ss.ss_currentRelation;
+	ExprContext	   *econtext = ctss->cps.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->cps.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->cps.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->cps.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(CustomPlanState *node)
+{
+	CtidScanState  *ctss = (CtidScanState *) node;
+	HeapScanDesc	scan;
+	TupleTableSlot *slot;
+	EState		   *estate = node->ss.ps.state;
+	ScanDirection	direction = estate->es_direction;
+	HeapTuple		tuple;
+
+	if (!ctss->cps.ss.ss_currentScanDesc)
+		ReScanCtidScan(node);
+	scan = ctss->cps.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->cps.ss.ss_ScanTupleSlot;
+	ExecStoreTuple(tuple, slot, scan->rs_cbuf, false);
+
+	return slot;
+}
+
+static bool
+CTidRecheckCustomScan(CustomPlanState *node, TupleTableSlot *slot)
+{
+	return true;
+}
+
+/*
+ * ExecCtidScan - A method of CustomPlanState; that fetches a tuple
+ * from the relation, if exist anymore.
+ */
+static TupleTableSlot *
+ExecCtidScan(CustomPlanState *node)
+{
+	return ExecScan(&node->ss,
+					(ExecScanAccessMtd) CTidAccessCustomScan,
+					(ExecScanRecheckMtd) CTidRecheckCustomScan);
+}
+
+/*
+ * CTidEndCustomScan - A method of CustomPlanState; that closes heap and
+ * scan descriptor, and release other related resources.
+ */
+static void
+EndCtidScan(CustomPlanState *node)
+{
+	CtidScanState  *ctss = (CtidScanState *)node;
+
+	if (ctss->cps.ss.ss_currentScanDesc)
+		heap_endscan(ctss->cps.ss.ss_currentScanDesc);
+}
+
+/*
+ * ExplanCtidScanTargetRel - A method of CustomPlanState; that output
+ * relation's name to be scanned.
+ */
+static void
+ExplanCtidScanTargetRel(CustomPlanState *node, ExplainState *es)
+{
+	CtidScanState  *ctss = (CtidScanState *) node;
+	CtidScanPlan   *ctid_plan = (CtidScanPlan *) ctss->cps.ss.ps.plan;
+	Index			rti = ctid_plan->cplan.scan.scanrelid;
+	RangeTblEntry   *rte;
+	char		   *objectname = NULL;
+	char		   *namespace = NULL;
+	char		   *refname;
+
+	/* logic copied from ExplainTargetRel */
+	rte = rt_fetch(rti, es->rtable);
+	refname = (char *) list_nth(es->rtable_names, rti - 1);
+	if (refname == NULL)
+		refname = rte->eref->aliasname;
+
+	Assert(rte->rtekind == RTE_RELATION);
+	objectname = get_rel_name(rte->relid);
+	if (es->verbose)
+		namespace = get_namespace_name(get_rel_namespace(rte->relid));
+
+	if (es->format == EXPLAIN_FORMAT_TEXT)
+	{
+		appendStringInfoString(es->str, " on");
+		if (namespace != NULL)
+			appendStringInfo(es->str, " %s.%s", quote_identifier(namespace),
+							 quote_identifier(objectname));
+		else if (objectname != NULL)
+			appendStringInfo(es->str, " %s", quote_identifier(objectname));
+		if (objectname == NULL || strcmp(refname, objectname) != 0)
+			appendStringInfo(es->str, " %s", quote_identifier(refname));
+	}
+	else
+	{
+		if (objectname != NULL)
+			ExplainPropertyText("Relation Name", objectname, es);
+		if (namespace != NULL)
+			ExplainPropertyText("Schema", namespace, es);
+		ExplainPropertyText("Alias", refname, es);
+	}
+}
+
+/*
+ * ExplainCtidScan - A method of CustomPlanState; that shows extra info
+ * on EXPLAIN command.
+ */
+static void
+ExplainCtidScan(CustomPlanState *node, List *ancestors, ExplainState *es)
+{
+	CtidScanState  *ctss = (CtidScanState *) node;
+	CtidScanPlan   *ctid_plan = (CtidScanPlan *) ctss->cps.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);
+	}
+}
+
+/*
+ * ExplainCtidPreScanNode - A method of CustomPlanState; that informs
+ * the core backend relation's rtindex to be referenced, prior to the
+ * main EXPLAIN processing.
+ */
+static void
+ExplainCtidPreScanNode(CustomPlanState *node, Bitmapset **rels_used)
+{
+	CtidScanState  *ctss = (CtidScanState *) node;
+	Index			scanrelid = ((Scan *)ctss->cps.ss.ps.plan)->scanrelid;
+
+	*rels_used = bms_add_member(*rels_used, scanrelid);
+}
+
+/*
+ * Entrypoint of this extension
+ */
+Datum
+CtidScanAddPath(PG_FUNCTION_ARGS)
+{
+	customScanArg  *cscan_arg = (customScanArg *)PG_GETARG_POINTER(0);
+	PlannerInfo	   *root;
+	RangeTblEntry  *rte;
+	RelOptInfo	   *baserel;
+	char			relkind;
+	ListCell	   *lc;
+	List		   *ctid_quals = NIL;
+
+	if (cscan_arg->custom_class != CUSTOMPLAN_CLASS_SCAN)
+		PG_RETURN_VOID();
+
+	root = cscan_arg->root;
+	rte = cscan_arg->rte;
+	baserel = cscan_arg->baserel;
+
+	/* all we can support is regular relations */
+	if (rte->rtekind != RTE_RELATION)
+		PG_RETURN_VOID();
+
+	relkind = get_rel_relkind(rte->relid);
+	if (relkind != RELKIND_RELATION &&
+		relkind != RELKIND_MATVIEW &&
+		relkind != RELKIND_TOASTVALUE)
+		PG_RETURN_VOID();
+
+	/* 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_CustomPlan;
+		ctid_path->cpath.path.parent = baserel;
+		ctid_path->cpath.path.param_info
+			= get_baserel_parampathinfo(root, baserel, required_outer);
+		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);
+	}
+	PG_RETURN_VOID();
+}
+PG_FUNCTION_INFO_V1(CtidScanAddPath);
+
+/*
+ * Entrypoint of this extension
+ */
+void
+_PG_init(void)
+{
+	/* setup ctidscan_path_methods */
+	ctidscan_path_methods.CustomName = "ctidscan";
+	ctidscan_path_methods.CreateCustomPlan = CreateCtidScanPlan;
+	ctidscan_path_methods.TextOutCustomPath = TextOutCtidScanPath;
+
+	/* setup ctidscan_plan_methods */
+	ctidscan_plan_methods.CustomName = "ctidscan";
+	ctidscan_plan_methods.InitCustomPlan = InitCtidScanPlan;
+	ctidscan_plan_methods.SetCustomPlanRef = SetCtidScanPlanRef;
+	ctidscan_plan_methods.SupportBackwardScan = SupportCtidBackwardScan;
+	ctidscan_plan_methods.FinalizeCustomPlan = FinalizeCtidScanPlan;
+	ctidscan_plan_methods.CreateCustomPlanState = CreateCtidScanState;
+	ctidscan_plan_methods.TextOutCustomPlan = TextOutCtidScanPlan;
+	ctidscan_plan_methods.CopyCustomPlan = CopyCtidScanPlan;
+
+	/* setup ctidscan_planstate_methods */
+	ctidscan_exec_methods.CustomName = "ctidscan";
+	ctidscan_exec_methods.BeginCustomPlan = BeginCtidScan;
+	ctidscan_exec_methods.ExecCustomPlan = ExecCtidScan;
+	ctidscan_exec_methods.EndCustomPlan = EndCtidScan;
+	ctidscan_exec_methods.ReScanCustomPlan = ReScanCtidScan;
+	ctidscan_exec_methods.MarkPosCustomPlan = NULL;
+	ctidscan_exec_methods.RestrPosCustomPlan = NULL;
+	ctidscan_exec_methods.ExplainCustomPlanTargetRel = ExplanCtidScanTargetRel;
+	ctidscan_exec_methods.ExplainCustomPlan = ExplainCtidScan;
+	ctidscan_exec_methods.ExplainCustomPreScanNode = ExplainCtidPreScanNode;
+	ctidscan_exec_methods.GetSpecialCustomVar = NULL;
+}
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..b0c1134
--- /dev/null
+++ b/contrib/ctidscan/expected/ctidscan.out
@@ -0,0 +1,312 @@
+--
+-- Regression Tests for Custom Plan APIs
+--
+CREATE EXTENSION ctidscan;
+-- 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                              
+----------------------------------------------------------------------
+ Custom (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 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 (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 (ctidscan) on t1
+               Filter: (ctid < '(2,10)'::tid)
+               ctid quals: (ctid < '(2,10)'::tid)
+   ->  Sort
+         Sort Key: t2.ctid
+         ->  Custom (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 EXECUTE p1('(5,0)'::tid, '(10,0)'::tid);
+                                       QUERY PLAN                                        
+-----------------------------------------------------------------------------------------
+ Custom (ctidscan) on t1  (cost=0.01..0.01 rows=1 width=43)
+   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 EXECUTE p1('(10,0)'::tid, '(5,0)'::tid);
+                                       QUERY PLAN                                        
+-----------------------------------------------------------------------------------------
+ Custom (ctidscan) on t1  (cost=0.01..5.00 rows=1 width=43)
+   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)
+
+-- 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..213a97a
--- /dev/null
+++ b/contrib/ctidscan/sql/ctidscan.sql
@@ -0,0 +1,54 @@
+--
+-- Regression Tests for Custom Plan APIs
+--
+
+CREATE EXTENSION ctidscan;
+
+-- 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;
+
+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);
+
+-- Test cleanup
+DROP SCHEMA regtest_custom_scan CASCADE;
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index b4a06e4..3d7a289 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -109,6 +109,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-custom-plan-provider"><structname>pg_custom_plan_provider</structname></link></entry>
+      <entry>custom plan providers</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-conversion"><structname>pg_conversion</structname></link></entry>
       <entry>encoding conversion information</entry>
      </row>
@@ -2508,6 +2513,60 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-custom-plan-provider">
+  <title><structname>pg_custom_plan_provider</structname></title>
+
+  <indexterm zone="catalog-pg-custom-plan-provider">
+   <primary>pg_custom_plan_provider</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_custom_plan_provider</structname> describes
+   custom-plan providers. See <xref linkend="sql-createcustomplanprovider">
+   for more information.
+  </para>
+
+  <table>
+   <title><structname>pg_custom_plan_provider</> Columns</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>References</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><structfield>oid</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry></entry>
+      <entry>Row identifier (hidden attribute; must be explicitly selected)</entry>
+     </row>
+     <row>
+      <entry><structfield>cppname</structfield></entry>
+      <entry><type>name</type></entry>
+      <entry></entry>
+      <entry>custom-plan provider name</entry>
+     </row>
+     <row>
+      <entry><structfield>cppclass</structfield></entry>
+      <entry><type>char</type></entry>
+      <entry></entry>
+      <entry>class of custom-plan node on which this custom-plan provider can perform</entry>
+     </row>
+     <row>
+      <entry><structfield>cpphandler</structfield></entry>
+      <entry><type>regproc</type></entry>
+      <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
+      <entry>handler function of custom-plan provder that will propose an alternative query execution path</entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
  <sect1 id="catalog-pg-database">
   <title><structname>pg_database</structname></title>
 
diff --git a/doc/src/sgml/custom-plan.sgml b/doc/src/sgml/custom-plan.sgml
new file mode 100755
index 0000000..320e5de
--- /dev/null
+++ b/doc/src/sgml/custom-plan.sgml
@@ -0,0 +1,431 @@
+<!-- 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 variable 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 the extension that provides custom-plan custom plan
+  provider.
+ </para>
+
+ <sect1 id="cpp-overall-steps">
+  <title>Custom Plan Overall Steps</title>
+  <para>
+   A custom plan provider can be registered using
+   <xref linkend="sql-createcustomplanprovider"> command.
+   It takes a class of custom plan and its handler function.
+   Class of custom plan specifies the task to be replaced by the custom-
+   plan being defined.
+   Only <literal>SCAN</> is the supported class of custom-plan right now.
+   Custom plan handler function is an entrypoint to the planner. It calls
+   the handler function during construction of query execution path.
+  </para>
+  <para>
+   Handler function has to be declared as a function that takes one
+   <literal>internal</> datatype and returns <literal>void</>.
+   On invocation, query planner derivers a pointer of data structure
+   according to the custom plan class, custom-plan handler function will
+   decide whether it can offer alternative execution path towards the
+   required task. If available, it can add a <literal>CustomPath</>
+   (or inherited object type) as one of the candidate execution path
+   with estimated cost and set of callbacks defined in the
+   <literal>CustomPathMethods</> structure.
+  </para>
+  <para>
+   Planner compares all the potential execution paths based on its cost.
+   Once a custom path that was added by custom plan provider gets chosen,
+   <literal>CreateCustomPlan</> callback shall be called, to populate
+   a <literal>CustomPlan</> (or inherited object type) node according to
+   the <literal>CustomPath</> node preliminary constructed.
+   In the similar manner, <literal>CreateCustomPlanState</> callback shall
+   be called, to populate a <literal>CustomPlanState</> (or inherited
+   object type) node according to the <literal>CustomPlan</> node being
+   constructed above.
+   The reason why custom-plan provider has to allocate a node object by
+   itself is that we allow to extend the base types to store private
+   fields managed by individual custom-plan providers, thus only custom-
+   plan provider knows actual data size to be allocated.
+  </para>
+  <para>
+   Once a <literal>CustomPlanState</> is constructed, its callback
+   functions are invoked by executor, so custom-plan provider performs
+   required tasks then pops up the result.
+  </para>
+ </sect1>
+
+ <sect1 id="cpp-handler-function">
+  <title>Custom Plan Handler Functions</title>
+  <para>
+   A handler function that was specified on
+   the <xref linkend="sql-createcustomplanprovider"> command is
+   declared to return <literal>void</> datatype and takes an
+   <literal>internal</> datatype; that is usually applied to exchange
+   internal data structure.
+   This handler function is not an exception. It can reference a pointer
+   being informed via function argument to understand the context.
+  </para>
+  <para>
+   The data structure of arguments fully depend on the class of custom-
+   plan. In case of <literal>SCAN</> class (that is only supported one
+   right now), the first argument points the following data structure
+   that has enough information to construct a <literal>Path</> node.
+<programlisting>
+typedef struct {
+    uint32          cpp_class;
+    PlannerInfo    *root;
+    RelOptInfo     *baserel;
+    RangeTblEntry  *rte;
+} customScanArg;
+</programlisting>
+   <literal>cpp_class</>is always <literal>CUSTOMPLAN_CLASS_SCAN</>
+   that shows this structure is for scan class.
+   <literal>root</> is <literal>PlannerInfo</> of this plan,
+   <literal>baserel</> is <literal>RelOptInfo</> of the relation to
+   be scanned, and <literal>rte</> is <literal>RangeTblEntry</> of
+   the relation.
+  </para>
+  <para>
+   The custom-plan provider being invoked can check whether it can
+   provides alternative way to scan the relation. If available, it
+   shall construct <literal>CustomPath</> or its inherited one with
+   estimated cost and callbacks below, then reguster the path using
+   <literal>add_path</> towards the supplied <literal>RelOptInfo</>.
+  </para>
+ </sect1>
+
+ <sect1 id="cpp-path-callbacks">
+  <title>Custom Path Callbacks</title>
+  <para>
+   This section introduces callback functions of <literal>CustomPath</>
+   structure; defined in the <literal>CustomPathMethods</>.
+  </para>
+  <para>
+<programlisting>
+Node *
+CreateCustomPlan(PlannerInfo *root,
+                 CustomPath *best_path);
+</programlisting>
+   It populates a <literal>CustomPlan</> (or inherited datatype) node
+   according to the supplied <literal>CustomPath</> node which was
+   constructed on the custom plan handler function then chosen by the
+   query planner.
+   Only custom plan provider can know exact size of the node to be
+   allocated, this callback allocate a <literal>CustomPlan</> node with
+   <literal>CustomPlanMethods</> callbacks table and arbitrary private
+   fields.
+  </para>
+  <para>
+   Note that the node tag can be assigned is either <literal>CustomPlan</>
+   or <literal>CustomPlanMarkPos</>. The later one indicates this plan
+   node supports mark and restore position during query execution, but
+   has identical data structure.
+  </para>
+
+
+  <para>
+<programlisting>
+void
+TextOutCustomPath(StringInfo str, const CustomPath *node);
+</programlisting>
+   It makes a text representation of custom path node. If custom-plan
+   provider extends <literal>CustomPath</> data type, it shall to put private
+   fields on the supplied <literal>StringInfo</> with text form.
+   Note that common fields in <literal>CustomPath</> are handled by backend,
+   so extension needs to do nothing special.
+  </para>
+ </sect1>
+
+ <sect1 id="cpp-plan-callbacks">
+  <title>Custom Plan Callbacks</title>
+  <para>
+   This section introduces callback functions of <literal>CustomPlan</>
+   structure; defined in the <literal>CustomPlanMethods</>.
+  </para>
+  <para>
+<programlisting>
+Plan      *
+InitCustomPlan(CustomPlan *custom_plan,
+               PlannerInfo *root,
+               CustomPath *best_path,
+               List *tlist,
+               List *clauses);
+</programlisting>
+   It initializes the <literal>CustomPlan</> node being acquired by
+   <literal>CreateCustomPlan</> callback.
+   The backend takes some common initializations prior to its invocation.
+   <literal>tlist</> and <literal>clauses</> are extracted from the path
+   node according to the usual manner, so all custom plan provider has to
+   do is putting these members if nothing special are done.
+  </para>
+
+  <para>
+<programlisting>
+void
+SetCustomPlanRef(PlannerInfo *root,
+                 CustomPlan *custom_plan,
+                 int rtoffset);
+</programlisting>
+   It adjusts <literal>varno</> and <literal>varattno</> of var-nodes in
+   the expression tree chained from <literal>CustomPlan</> 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 <literal>fix_scan_expr</> shall be called on expression nodes to
+   track plan dependency.
+  </para>
+
+  <para>
+<programlisting>
+bool
+SupportBackwardScan(CustomPlan *custom_plan);
+</programlisting>
+   It is an optional callback, which tells the backend whether this custom-
+   plan node supports backward scan or not. If <literal>NULL</>, it means
+   backward scan is not supported.
+  </para>
+
+  <para>
+<programlisting>
+void
+FinalizeCustomPlan(PlannerInfo *root,
+                   CustomPlan *custom_plan,
+                   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 custom-plan 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 custom-plan 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 *
+CreateCustomPlanState(CustomPlan *custom_plan);
+</programlisting>
+   It populates a <literal>CustomPlanState</> (or inherited datatype)
+   node according to the supplied <literal>CustomPlan</> node preliminary
+   constructed, on the beginning of query executor.
+   Only custom plan provider can know exact size of the node to be
+   allocated, this callback allocate a <literal>CustomPlanState</> node
+   with <literal>CustomExecMethods</> callbacks table and arbitrary
+   private fields.
+  </para>
+  <para>
+   Note that main purpose of this callback is allocation of
+   <literal>CustomPlanState</> node, not initialization of individual
+   fields because it shall be handled on the <literal>BeginCustomPlan</>
+   callback to be invoked next to the common usual initialization.
+  </para>
+
+  <para>
+<programlisting>
+void
+TextOutCustomPlan(StringInfo str,
+                  const CustomPlan *node);
+</programlisting>
+   It makes a text representation of custom plan node. If custom-plan
+   provider extends <literal>CustomPlan</> data type, it shall put private
+   fields on the supplied <literal>StringInfo</> with text form.
+   Note that common fields in <literal>CustomPlan</> are handled by backend,
+   so extension needs to do nothing special.
+  </para>
+
+  <para>
+<programlisting>
+CustomPlan *
+CopyCustomPlan(const CustomPlan *from);
+</programlisting>
+   It duplicate a <literal>CustomPlan</> node onto a newly allocated one.
+   If custom-plan provider extends the <literal>CustomPlan</> node, it shall
+   copy the private fields and callback table but no need to copy the common
+   <literal>scan</> field in the <literal>CustomPlan</> data type.
+  </para>
+ </sect1>
+
+ <sect1 id="cpp-exec-callbacks">
+  <title>Custom Executor Callbacks</title>
+  <para>
+   This section introduces callback functions of <literal>CustomPlanState</>
+   structure; defined in the <literal>CustomExecMethods</>.
+  </para>
+  <para>
+<programlisting>
+void
+BeginCustomPlan(CustomPlanState *node,
+                EState *estate,
+                int eflags);
+</programlisting>
+   It begins execution of custom-plan. This callback is invoked during
+   executor startup to initialize the supplied <literal>CustomPlanState</>
+   that was constructed on the <literal>CreateCustomPlanState</> above.
+   The custom-plan provider shall have initialization of its private fields
+   and common fields within <literal>CustomPlanState</> if needed, because
+   backend code already initializes expressions on its <literal>targetlist</>
+   and <literal>qual</>, assigns result slot according to the
+   <literal>targetlist</> and also assigns scan slot if <literal>scanrelid</>
+   is valid.
+  </para>
+
+  <para>
+<programlisting>
+TupleTableSlot *
+ExecCustomPlan(CustomPlanState *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
+   <literal>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 <literal>EState</>, to acquire per-scan
+   duration memory.
+  </para>
+
+  <para>
+<programlisting>
+Node *
+MultiExecCustomPlan(CustomPlanState *node);
+</programlisting>
+   It allows to return arbitrary data structure to the upper node, unlike
+   usual <literal>ExecCustomPlan</>. Built-in code has no invocation path
+   to call <literal>MultiExecProcNode</> towards <literal>CustomPlanState</>
+   node, so it is invoked only when a particular custom-plan provider made
+   a stacked custom-plan nodes and called <literal>MultiExecProcNode</> to
+   this underlying node.
+  </para>
+  <para>
+   Note that it is custom-plan provider's responsibility to translate the
+   arbitrary data structure into <productname>PostgreSQL</>'s complianced
+   data structure when top-level <literal>CustomPlanState</> returns a row
+   using <literal>ExecCustomPlan</>.
+  </para>
+
+  <para>
+<programlisting>
+void
+EndCustomPlan(CustomPlanState *node);
+</programlisting>
+   It ends the execution of custom plan and release any resources held by
+   this node. If custom-plan provider acquired resources that is not
+   released automatically at end of executor, it is responsibility of the
+   custom plan provider.
+  </para>
+
+  <para>
+<programlisting>
+void
+ReScanCustomPlan(CustomPlanState *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 *
+MarkPosCustomPlan(CustomPlanState *node);
+</programlisting>
+   It is an optional callback, if <literal>CustomPlanState</> was populated
+   from the <literal>CustomPlanMarkPos</>. Elsewhere, it shall not be called
+   and <literal>NULL</> is suitable.
+   It saves current scan position on somewhere in private fields of
+   <literal>CustomPlanState</>, to restore the position later.
+  </para>
+
+  <para>
+<programlisting>
+void *
+RestrPosCustomPlan(CustomPlanState *node);
+</programlisting>
+   It is an optional callback, if <literal>CustomPlanState</> was populated
+   from the <literal>CustomPlanMarkPos</>. Elsewhere, it shall not be called
+   and <literal>NULL</> is suitable.
+   It restores the previous scan position saved by
+   the <literal>MarkPosCustomPlan</> above.
+  </para>
+
+  <para>
+<programlisting>
+void
+ExplainCustomPlanTargetRel(CustomPlanState *node,
+                           ExplainState *es);
+</programlisting>
+   It is an optional callback, to show the target relation to be scanned.
+   In most cases, custom plan provider put text representation of the relation
+   to be scanned according to the manner in <literal>ExplainTargetRel</>.
+  </para>
+
+  <para>
+<programlisting>
+void
+ExplainCustomPlan(CustomPlanState *node,
+                  List *ancestors,
+                  ExplainState *es);
+</programlisting>
+   It is an optional callback, to show custom-plan specific explain output.
+  </para>
+
+  <para>
+<programlisting>
+void
+ExplainCustomPreScanNode(CustomPlanState *node,
+                         Bitmapset **rels_used);
+</programlisting>
+   It is an optional callback, to inform the backend which relation is
+   referenced. It shall set <literal>scanrelid</> of the target relation.
+   If <literal>NULL</>, it means this custom-plan provider never
+   references base relations.
+  </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>CustomPlanState</> when <literal>EXPLAIN</> needs the
+   text form of the column actually referenced.
+   In case when custom-plan provider adjusted <literal>varno</> of varnodes
+   on the expression tree to use special varnos (<literal>INNER_VAR</>,
+   <literal>OUTER_VAR</> or <literal>INDEX_VAR</>), custom-plan 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 <literal>PlanState</> that shall be
+   set on the <literal>child_ps</> argument.
+  </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/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index 1b0962c..4103baa 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -55,6 +55,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY createCast         SYSTEM "create_cast.sgml">
 <!ENTITY createCollation    SYSTEM "create_collation.sgml">
 <!ENTITY createConversion   SYSTEM "create_conversion.sgml">
+<!ENTITY createCustomPlanProvider SYSTEM "create_custom_plan_provider.sgml">
 <!ENTITY createDatabase     SYSTEM "create_database.sgml">
 <!ENTITY createDomain       SYSTEM "create_domain.sgml">
 <!ENTITY createEventTrigger SYSTEM "create_event_trigger.sgml">
@@ -95,6 +96,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY dropCast           SYSTEM "drop_cast.sgml">
 <!ENTITY dropCollation      SYSTEM "drop_collation.sgml">
 <!ENTITY dropConversion     SYSTEM "drop_conversion.sgml">
+<!ENTITY dropCustomPlanProvider SYSTEM "drop_custom_plan_provider.sgml">
 <!ENTITY dropDatabase       SYSTEM "drop_database.sgml">
 <!ENTITY dropDomain         SYSTEM "drop_domain.sgml">
 <!ENTITY dropEventTrigger   SYSTEM "drop_event_trigger.sgml">
diff --git a/doc/src/sgml/ref/create_custom_plan_provider.sgml b/doc/src/sgml/ref/create_custom_plan_provider.sgml
new file mode 100644
index 0000000..0816584
--- /dev/null
+++ b/doc/src/sgml/ref/create_custom_plan_provider.sgml
@@ -0,0 +1,139 @@
+<!--
+doc/src/sgml/ref/create_custom_plan_provider.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-CREATECUSTOMPLANPROVIDER">
+ <indexterm zone="sql-createcustomplanprovider">
+  <primary>CREATE CUSTOM PLAN PROVIDER</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>CREATE CUSTOM PLAN PROVIDER</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>CREATE CUSTOM PLAN PROVIDER</refname>
+  <refpurpose>define a new custom plan provider</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+CREATE CUSTOM PLAN PROVIDER <replaceable class="parameter">cpp_name</replaceable> FOR <replaceable class="parameter">cpp_class</replaceable>
+    HANDLER <replaceable class="parameter">handler_function</replaceable>
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>CREATE CUSTOM PLAN PROVIDER</command> defines a new custom-plan
+   provider.
+   The user who defines the custom-plan provider has to be a superuser.
+  </para>
+
+  <para>
+   A custom-plan provider can offer the query planner alternative options
+   to scan relation, or potentially join relations and so on, in addition
+   to the built-in logics. It is usually extension modules that implement
+   callbacks according to the custom-plan interface.
+  </para>
+  <para>
+   This statement defines a couple of an entrypoint of custom-plan provider
+   and its supporting workload type. Right now, <literal>scan</literal> is
+   the only class being supported; that enables to call extension's
+   callback during query execution instead of built-in routines like
+   <literal>SeqScan</literal> or <literal>IndexScan</literal> if its
+   cost estimation is enough reasonable.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">cpp_name</replaceable></term>
+    <listitem>
+     <para>
+      The name of the custom-plan provider to be created.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">cpp_class</replaceable></term>
+    <listitem>
+     <para>
+      Workload type on which custom-plan provider can perform.
+      Only <literal>SCAN</literal> is supported option right now.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">handler_function</replaceable></term>
+    <listitem>
+     <para>
+      A function to be called when query planner is finding the best path
+      to scan a relation.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+   The function that performs as a custom-plan provider shall be declared
+   to return <literal>void</> and take one argument with <literal>internal</>
+   data type.
+   The core <productname>PostgreSQL</> calls custom-plan provider function
+   with a set of information about planner's state and relation(s) to be
+   scanned, then this function shall check whether it can offer alternative
+   scan paths or not.
+   If available, it constructs a path object derived from
+   <literal>CustomPath</> structure, that contains a set of callbacks
+   including the ones to populate <literal>CustomPlan</> or
+   <literal>CustomPlanState</> object later.
+   If extension needs to save its private information in these object,
+   define a new structure that extends above data types.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+  <para>
+   Create a custom-plan provider <literal>ctidscan</> that uses the funcion
+   <literal>ctidscanaddpath</>.   
+<programlisting>
+CREATE CUSTOM PLAN PROVIDER ctidscan FOR scan HANDLER ctidscanaddpath;
+</programlisting>
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+  <para>
+   There is no <command>CREATE CUSTOM PLAN</command> command
+   in the SQL standard.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-dropcustomplanprovider"></member>
+  </simplelist>
+  <simplelist type="inline">
+   <member><xref linkend="custom-plan"></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/drop_custom_plan_provider.sgml b/doc/src/sgml/ref/drop_custom_plan_provider.sgml
new file mode 100644
index 0000000..6a305a8
--- /dev/null
+++ b/doc/src/sgml/ref/drop_custom_plan_provider.sgml
@@ -0,0 +1,108 @@
+<!--
+doc/src/sgml/ref/drop_custom_plan_provider.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-DROPCUSTOMPLANPROVIDER">
+ <indexterm zone="sql-dropcustomplanprovider">
+  <primary>DROP CUSTOM PLAN PROVIDER</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>DROP CUSTOM PLAN PROVIDER</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>DROP CUSTOM PLAN PROVIDER</refname>
+  <refpurpose>remove a custom-plan provider</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+DROP CUSTOM PLAN PROVIDER [ IF EXISTS ] <replaceable class="parameter">cpp_name</replaceable> [ CASCADE | RESTRICT ]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>DROP CUSTOM PLAN PROVIDER</command> removes an existing custom
+   plan provider. To execute this command, the current user must be superuser.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>IF EXISTS</literal></term>
+    <listitem>
+     <para>
+      Do not throw an error if the custom-plan provider does not exist.
+      A notice is issued in this case.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name of an existing custom-plan provider.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>CASCADE</literal></term>
+    <listitem>
+     <para>
+      Automatically drop objects that depend on the custom-plan provider.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>RESTRICT</literal></term>
+    <listitem>
+     <para>
+      Refuse to drop the custom-plan provider if any objects depend on it.
+      This is the default.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   Drop a custom-plan provider <literal>foo</> if it exists:
+<programlisting>
+DROP CUSTOM PLAN PROVIDER IF EXISTS foo;
+</programlisting></para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   There is no <command>DROP CUSTOM PLAN</command> command
+   in the SQL standard.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-createcustomplanprovider"></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index a6575f5..48b499c 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -83,6 +83,7 @@
    &createCast;
    &createCollation;
    &createConversion;
+   &createCustomPlanProvider;
    &createDatabase;
    &createDomain;
    &createEventTrigger;
@@ -123,6 +124,7 @@
    &dropCast;
    &dropCollation;
    &dropConversion;
+   &dropCustomPlanProvider;
    &dropDatabase;
    &dropDomain;
    &dropEventTrigger;
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index a974bd5..f7e29eb 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -39,7 +39,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
 	pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
 	pg_ts_parser.h pg_ts_template.h pg_extension.h \
 	pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
-	pg_foreign_table.h \
+	pg_foreign_table.h pg_custom_plan_provider.h \
 	pg_default_acl.h pg_seclabel.h pg_shseclabel.h pg_collation.h pg_range.h \
 	toasting.h indexing.h \
     )
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index d41ba49..496bc9a 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -30,6 +30,7 @@
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_conversion.h"
 #include "catalog/pg_conversion_fn.h"
+#include "catalog/pg_custom_plan_provider.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_default_acl.h"
 #include "catalog/pg_depend.h"
@@ -154,7 +155,8 @@ static const Oid object_classes[MAX_OCLASS] = {
 	UserMappingRelationId,		/* OCLASS_USER_MAPPING */
 	DefaultAclRelationId,		/* OCLASS_DEFACL */
 	ExtensionRelationId,		/* OCLASS_EXTENSION */
-	EventTriggerRelationId		/* OCLASS_EVENT_TRIGGER */
+	EventTriggerRelationId,		/* OCLASS_EVENT_TRIGGER */
+	CustomPlanProviderRelationId,		/* OCLASS_CPP */
 };
 
 
@@ -1249,6 +1251,10 @@ doDeletion(const ObjectAddress *object, int flags)
 			RemoveEventTriggerById(object->objectId);
 			break;
 
+		case OCLASS_CPP:
+			RemoveCustomPlanProviderById(object->objectId);
+			break;
+
 		default:
 			elog(ERROR, "unrecognized object class: %u",
 				 object->classId);
@@ -2316,6 +2322,9 @@ getObjectClass(const ObjectAddress *object)
 
 		case EventTriggerRelationId:
 			return OCLASS_EVENT_TRIGGER;
+
+		case CustomPlanProviderRelationId:
+			return OCLASS_CPP;
 	}
 
 	/* shouldn't get here */
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index c7c8f4b..2b0b0e1 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -30,6 +30,7 @@
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_conversion.h"
+#include "catalog/pg_custom_plan_provider.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_extension.h"
 #include "catalog/pg_foreign_data_wrapper.h"
@@ -152,6 +153,18 @@ static const ObjectPropertyType ObjectProperty[] =
 		true
 	},
 	{
+		CustomPlanProviderRelationId,
+		CustomPlanProviderOidIndexId,
+		CUSTOMPLANPROVIDEROID,
+		CUSTOMPLANPROVIDERNAME,
+		Anum_pg_custom_plan_provider_cppname,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		-1,
+		false,
+	},
+	{
 		DatabaseRelationId,
 		DatabaseOidIndexId,
 		DATABASEOID,
@@ -529,6 +542,7 @@ get_object_address(ObjectType objtype, List *objname, List *objargs,
 			case OBJECT_FDW:
 			case OBJECT_FOREIGN_SERVER:
 			case OBJECT_EVENT_TRIGGER:
+			case OBJECT_CPP:
 				address = get_object_address_unqualified(objtype,
 														 objname, missing_ok);
 				break;
@@ -755,6 +769,9 @@ get_object_address_unqualified(ObjectType objtype,
 			case OBJECT_EVENT_TRIGGER:
 				msg = gettext_noop("event trigger name cannot be qualified");
 				break;
+			case OBJECT_CPP:
+				msg = gettext_noop("custom plan provider name cannot be qualified");
+				break;
 			default:
 				elog(ERROR, "unrecognized objtype: %d", (int) objtype);
 				msg = NULL;		/* placate compiler */
@@ -815,6 +832,11 @@ get_object_address_unqualified(ObjectType objtype,
 			address.objectId = get_event_trigger_oid(name, missing_ok);
 			address.objectSubId = 0;
 			break;
+		case OBJECT_CPP:
+			address.classId = CustomPlanProviderRelationId;
+			address.objectId = get_custom_plan_provider_oid(name, missing_ok);
+			address.objectSubId = 0;
+			break;
 		default:
 			elog(ERROR, "unrecognized objtype: %d", (int) objtype);
 			/* placate compiler, which doesn't know elog won't return */
@@ -1295,6 +1317,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
 			break;
 		case OBJECT_TSPARSER:
 		case OBJECT_TSTEMPLATE:
+		case OBJECT_CPP:
 			/* We treat these object types as being owned by superusers */
 			if (!superuser_arg(roleid))
 				ereport(ERROR,
@@ -2166,6 +2189,24 @@ getObjectDescription(const ObjectAddress *object)
 				break;
 			}
 
+		case OCLASS_CPP:
+			{
+				Form_pg_custom_plan_provider cpp_form;
+				HeapTuple	tup;
+
+				tup = SearchSysCache1(CUSTOMPLANPROVIDEROID,
+									  ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR,
+						 "cache lookup failed for custom-plan provider %u",
+						 object->objectId);
+				cpp_form = (Form_pg_custom_plan_provider) GETSTRUCT(tup);
+				appendStringInfo(&buffer, _("custom plan provider %s"),
+								 NameStr(cpp_form->cppname));
+				ReleaseSysCache(tup);
+				break;
+			}
+
 		default:
 			appendStringInfo(&buffer, "unrecognized object %u %u %d",
 							 object->classId,
@@ -2577,6 +2618,10 @@ getObjectTypeDescription(const ObjectAddress *object)
 			appendStringInfoString(&buffer, "event trigger");
 			break;
 
+		case OCLASS_CPP:
+			appendStringInfoString(&buffer, "custom plan provider");
+			break;
+
 		default:
 			appendStringInfo(&buffer, "unrecognized %u", object->classId);
 			break;
@@ -3330,6 +3375,24 @@ getObjectIdentity(const ObjectAddress *object)
 				break;
 			}
 
+		case OCLASS_CPP:
+			{
+				HeapTuple	tup;
+				Form_pg_custom_plan_provider cpp_form;
+
+				tup = SearchSysCache1(CUSTOMPLANPROVIDEROID,
+									  ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR,
+						 "cache lookup failed for custom-plan provider %u",
+						 object->objectId);
+				cpp_form = (Form_pg_custom_plan_provider) GETSTRUCT(tup);
+				appendStringInfoString(&buffer,
+						 quote_identifier(NameStr(cpp_form->cppname)));
+				ReleaseSysCache(tup);
+				break;
+			}
+
 		default:
 			appendStringInfo(&buffer, "unrecognized object %u %u %d",
 							 object->classId,
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index 22f116b..1e8e6f4 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -14,7 +14,7 @@ include $(top_builddir)/src/Makefile.global
 
 OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o  \
 	collationcmds.o constraint.o conversioncmds.o copy.o createas.o \
-	dbcommands.o define.o discard.o dropcmds.o \
+	custom_plan.o dbcommands.o define.o discard.o dropcmds.o \
 	event_trigger.o explain.o extension.o foreigncmds.o functioncmds.o \
 	indexcmds.o lockcmds.o matview.o operatorcmds.o opclasscmds.o \
 	portalcmds.o prepare.o proclang.o \
diff --git a/src/backend/commands/custom_plan.c b/src/backend/commands/custom_plan.c
new file mode 100644
index 0000000..2a81a7b
--- /dev/null
+++ b/src/backend/commands/custom_plan.c
@@ -0,0 +1,188 @@
+/*-------------------------------------------------------------------------
+ *
+ * custom_plan.c
+ *		custom plan nodes creation/manipulation commands
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ *    src/backend/commands/custom_plan.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_custom_plan_provider.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+#include "commands/defrem.h"
+#include "fmgr.h"
+#include "miscadmin.h"
+#include "parser/parse_func.h"
+#include "utils/builtins.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+/*
+ * utility function to lookup a custom-plan provider by name
+ */
+Oid
+get_custom_plan_provider_oid(const char *cpp_name, bool missing_ok)
+{
+	Oid		cpp_oid;
+
+	cpp_oid = GetSysCacheOid1(CUSTOMPLANPROVIDERNAME,
+							   CStringGetDatum(cpp_name));
+	if (!OidIsValid(cpp_oid) && !missing_ok)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("custom-plan provider \"%s\" does not exist",
+						cpp_name)));
+	return cpp_oid;
+}
+
+/*
+ * Drop a custom-plan provider
+ */
+void
+RemoveCustomPlanProviderById(Oid cpp_oid)
+{
+	Relation	rel;
+	HeapTuple	tuple;
+
+	rel = heap_open(CustomPlanProviderRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCache1(CUSTOMPLANPROVIDEROID,
+							ObjectIdGetDatum(cpp_oid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for custom-plan provider %u",
+			 cpp_oid);
+
+	simple_heap_delete(rel, &tuple->t_self);
+
+	ReleaseSysCache(tuple);
+
+	heap_close(rel, RowExclusiveLock);
+}
+
+/*
+ * Create a custom-plan provider
+ */
+Oid
+DefineCustomPlanProvider(CreateCustomPlanProviderStmt *stmt)
+{
+	Relation	rel;
+	Oid			cpp_oid;
+	Oid			cpp_handler = InvalidOid;
+	Datum		values[Natts_pg_custom_plan_provider];
+	bool		isnull[Natts_pg_custom_plan_provider];
+	HeapTuple	tuple;
+	ListCell   *cell;
+	ObjectAddress myself;
+	ObjectAddress referenced;
+
+	rel = heap_open(CustomPlanProviderRelationId, RowExclusiveLock);
+
+	/* must be super user */
+	if (!superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+			 errmsg("permission denied to create custom-plan provider \"%s\"",
+					stmt->cpp_name),
+			 errhint("Must be superuser to create a custom-plan node.")));
+
+	/* check namespace conflicts */
+	cpp_oid = get_custom_plan_provider_oid(stmt->cpp_name, true);
+	if (OidIsValid(cpp_oid))
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("custom-plan provider \"%s\" already exists",
+						stmt->cpp_name)));
+
+	/* check custom-plan class */
+	if (stmt->cpp_class != CUSTOMPLAN_CLASS_SCAN)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("unexpected custom plan class specified: %d",
+						(int)stmt->cpp_class)));
+
+	/* parse custom-plan options */
+	foreach (cell, stmt->cpp_options)
+	{
+		DefElem	   *defel = lfirst(cell);
+
+		Assert(IsA(defel, DefElem));
+
+		if (strcmp(defel->defname, "handler") == 0)
+		{
+			Oid		argtypes[1];
+
+			if (OidIsValid(cpp_handler))
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+
+			argtypes[0] = INTERNALOID;
+			cpp_handler = LookupFuncName((List *)defel->arg,
+										 1, argtypes, false);
+			if (get_func_rettype(cpp_handler) != VOIDOID)
+				ereport(ERROR,
+						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+						 errmsg("function %s must return type \"void\"",
+								NameListToString((List *) defel->arg))));
+		}
+		else
+			elog(ERROR, "unexpected custom-plan provider option: %s",
+				 defel->defname);
+	}
+
+	if (!OidIsValid(cpp_handler))
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("HANDLER must be provided")));
+
+	/*
+	 * Insert tuple into pg_custom_plan system catalog
+	 */
+	memset(values, 0, sizeof(values));
+	memset(isnull, 0, sizeof(isnull));
+	values[Anum_pg_custom_plan_provider_cppname - 1]
+		= DirectFunctionCall1(namein, CStringGetDatum(stmt->cpp_name));
+	values[Anum_pg_custom_plan_provider_cppclass - 1]
+		= stmt->cpp_class;
+	values[Anum_pg_custom_plan_provider_cpphandler - 1]
+		= ObjectIdGetDatum(cpp_handler);
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), values, isnull);
+
+	cpp_oid = simple_heap_insert(rel, tuple);
+	CatalogUpdateIndexes(rel, tuple);
+
+	heap_freetuple(tuple);
+
+	/* record dependencies */
+	myself.classId = CustomPlanProviderRelationId;
+	myself.objectId = cpp_oid;
+	myself.objectSubId = 0;
+
+	referenced.classId = ProcedureRelationId;
+	referenced.objectId = cpp_handler;
+	referenced.objectSubId = 0;
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+	/* Post creation hook for new custom-plan provider */
+	InvokeObjectPostCreateHook(CustomPlanProviderRelationId, cpp_oid, 0);
+
+	heap_close(rel, RowExclusiveLock);
+
+	return cpp_oid;
+}
diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c
index e64ad80..c6d4576 100644
--- a/src/backend/commands/dropcmds.c
+++ b/src/backend/commands/dropcmds.c
@@ -408,6 +408,11 @@ does_not_exist_skipping(ObjectType objtype, List *objname, List *objargs)
 				args = strVal(linitial(objargs));
 			}
 			break;
+		case OBJECT_CPP:
+			msg = gettext_noop("custom-plan provider \"%s\" does not exist, skipping");
+			name = NameListToString(objname);
+			break;
+
 		default:
 			elog(ERROR, "unexpected object type (%d)", (int) objtype);
 			break;
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 110fe00..751a5f1 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -922,6 +922,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
 		case OBJECT_CONSTRAINT:
 		case OBJECT_COLLATION:
 		case OBJECT_CONVERSION:
+		case OBJECT_CPP:
 		case OBJECT_DOMAIN:
 		case OBJECT_EXTENSION:
 		case OBJECT_FDW:
@@ -974,6 +975,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
 		case OCLASS_COLLATION:
 		case OCLASS_CONSTRAINT:
 		case OCLASS_CONVERSION:
+		case OCLASS_CPP:
 		case OCLASS_DEFAULT:
 		case OCLASS_LANGUAGE:
 		case OCLASS_LARGEOBJECT:
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 0d9663c..a4e1344 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -722,6 +722,15 @@ ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
 			*rels_used = bms_add_member(*rels_used,
 										((Scan *) plan)->scanrelid);
 			break;
+		case T_CustomPlan:
+		case T_CustomPlanMarkPos:
+			{
+				CustomPlanState *cps = (CustomPlanState *)planstate;
+
+				if (cps->methods->ExplainCustomPreScanNode)
+					cps->methods->ExplainCustomPreScanNode(cps, rels_used);
+			}
+			break;
 		case T_ModifyTable:
 			/* cf ExplainModifyTarget */
 			*rels_used = bms_add_member(*rels_used,
@@ -848,6 +857,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 +946,14 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_ForeignScan:
 			pname = sname = "Foreign Scan";
 			break;
+		case T_CustomPlan:
+			sname = "Custom";
+			custom_name = ((CustomPlan *) plan)->methods->CustomName;
+			if (custom_name)
+				pname = psprintf("Custom (%s)", custom_name);
+			else
+				pname = sname;
+			break;
 		case T_Material:
 			pname = sname = "Materialize";
 			break;
@@ -1037,6 +1055,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", custom_name, es);
 	}
 
 	switch (nodeTag(plan))
@@ -1084,6 +1104,14 @@ ExplainNode(PlanState *planstate, List *ancestors,
 					ExplainPropertyText("Index Name", indexname, es);
 			}
 			break;
+		case T_CustomPlan:
+			{
+				CustomPlanState *cps = (CustomPlanState *)planstate;
+
+				if (cps->methods->ExplainCustomPlanTargetRel)
+					cps->methods->ExplainCustomPlanTargetRel(cps, es);
+			}
+			break;
 		case T_ModifyTable:
 			ExplainModifyTarget((ModifyTable *) plan, es);
 			break;
@@ -1353,6 +1381,18 @@ ExplainNode(PlanState *planstate, List *ancestors,
 										   planstate, es);
 			show_foreignscan_info((ForeignScanState *) planstate, es);
 			break;
+		case T_CustomPlan:
+			{
+				CustomPlanState *cps = (CustomPlanState *) planstate;
+
+				show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
+				if (plan->qual)
+					show_instrumentation_count("Rows Removed by Filter", 1,
+											   planstate, es);
+				if (cps->methods->ExplainCustomPlan)
+					cps->methods->ExplainCustomPlan(cps, 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..800c969 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -21,6 +21,7 @@
 #include "executor/nodeBitmapIndexscan.h"
 #include "executor/nodeBitmapOr.h"
 #include "executor/nodeCtescan.h"
+#include "executor/nodeCustom.h"
 #include "executor/nodeForeignscan.h"
 #include "executor/nodeFunctionscan.h"
 #include "executor/nodeGroup.h"
@@ -197,6 +198,10 @@ ExecReScan(PlanState *node)
 			ExecReScanForeignScan((ForeignScanState *) node);
 			break;
 
+		case T_CustomPlanState:
+			ExecReScanCustomPlan((CustomPlanState *) node);
+			break;
+
 		case T_NestLoopState:
 			ExecReScanNestLoop((NestLoopState *) node);
 			break;
@@ -291,6 +296,10 @@ ExecMarkPos(PlanState *node)
 			ExecValuesMarkPos((ValuesScanState *) node);
 			break;
 
+		case T_CustomPlanState:
+			ExecCustomMarkPos((CustomPlanState *) node);
+			break;
+
 		case T_MaterialState:
 			ExecMaterialMarkPos((MaterialState *) node);
 			break;
@@ -348,6 +357,10 @@ ExecRestrPos(PlanState *node)
 			ExecValuesRestrPos((ValuesScanState *) node);
 			break;
 
+		case T_CustomPlanState:
+			ExecCustomRestrPos((CustomPlanState *) node);
+			break;
+
 		case T_MaterialState:
 			ExecMaterialRestrPos((MaterialState *) node);
 			break;
@@ -390,6 +403,7 @@ ExecSupportsMarkRestore(NodeTag plantype)
 		case T_ValuesScan:
 		case T_Material:
 		case T_Sort:
+		case T_CustomPlanMarkPos:
 			return true;
 
 		case T_Result:
@@ -465,6 +479,17 @@ ExecSupportsBackwardScan(Plan *node)
 			return ExecSupportsBackwardScan(((SubqueryScan *) node)->subplan) &&
 				TargetListSupportsBackwardScan(node->targetlist);
 
+		case T_CustomPlan:
+			{
+				CustomPlan *cplan = (CustomPlan *) node;
+
+				if (!TargetListSupportsBackwardScan(node->targetlist))
+					return false;
+				if (cplan->methods->SupportBackwardScan)
+					return cplan->methods->SupportBackwardScan(cplan);
+			}
+			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..62ebab9 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,12 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
 													   estate, eflags);
 			break;
 
+		case T_CustomPlan:
+		case T_CustomPlanMarkPos:
+			result = (PlanState *) ExecInitCustomPlan((CustomPlan *) node,
+													  estate, eflags);
+			break;
+
 			/*
 			 * join nodes
 			 */
@@ -442,6 +449,10 @@ ExecProcNode(PlanState *node)
 			result = ExecForeignScan((ForeignScanState *) node);
 			break;
 
+		case T_CustomPlanState:
+			result = ExecCustomPlan((CustomPlanState *) node);
+			break;
+
 			/*
 			 * join nodes
 			 */
@@ -558,6 +569,10 @@ MultiExecProcNode(PlanState *node)
 			result = MultiExecBitmapOr((BitmapOrState *) node);
 			break;
 
+		case T_CustomPlanState:
+			result = MultiExecCustomPlan((CustomPlanState *) node);
+			break;
+
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
 			result = NULL;
@@ -678,6 +693,10 @@ ExecEndNode(PlanState *node)
 			ExecEndForeignScan((ForeignScanState *) node);
 			break;
 
+		case T_CustomPlanState:
+			ExecEndCustomPlan((CustomPlanState *) node);
+			break;
+
 			/*
 			 * join nodes
 			 */
diff --git a/src/backend/executor/nodeCustom.c b/src/backend/executor/nodeCustom.c
new file mode 100644
index 0000000..785909e
--- /dev/null
+++ b/src/backend/executor/nodeCustom.c
@@ -0,0 +1,147 @@
+/* ------------------------------------------------------------------------
+ *
+ * nodeCustom.c
+ *		Routines to handle execution of custom plan 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"
+
+CustomPlanState *
+ExecInitCustomPlan(CustomPlan *cplan, EState *estate, int eflags)
+{
+	CustomPlanState    *cps;
+
+	/* populate a CustomPlanState according to the CustomPlan */
+	cps = (CustomPlanState *)cplan->methods->CreateCustomPlanState(cplan);
+	Assert(IsA(cps, CustomPlanState));
+
+	/* fill up fields of PlanState */
+	cps->ss.ps.plan = &cplan->scan.plan;
+	cps->ss.ps.state = estate;
+
+	/* create expression context for node */
+	ExecAssignExprContext(estate, &cps->ss.ps);
+	cps->ss.ps.ps_TupFromTlist = false;
+
+	/* initialize child expressions */
+	cps->ss.ps.targetlist = (List *)
+		ExecInitExpr((Expr *) cplan->scan.plan.targetlist,
+					 (PlanState *) cps);
+	cps->ss.ps.qual = (List *)
+		ExecInitExpr((Expr *) cplan->scan.plan.qual,
+					 (PlanState *) cps);
+
+	/* initialization of result tuple slot  */
+	ExecInitResultTupleSlot(estate, &cps->ss.ps);
+	ExecAssignResultTypeFromTL(&cps->ss.ps);
+
+	if (cplan->scan.scanrelid > 0)
+	{
+		Relation	heap_rel;
+
+		heap_rel = ExecOpenScanRelation(estate, cplan->scan.scanrelid, eflags);
+		cps->ss.ss_currentRelation = heap_rel;
+		cps->ss.ss_currentScanDesc = NULL;	/* set by provider on demand */
+		ExecInitScanTupleSlot(estate, &cps->ss);
+		ExecAssignScanType(&cps->ss, RelationGetDescr(heap_rel));
+		ExecAssignScanProjectionInfo(&cps->ss);
+	}
+	else
+	{
+		/*
+		 * Elsewhere, custom-plan provider should be responsible to put
+		 * appropriate initialization of scan tuple-slot and projection
+		 * info.
+		 */
+		cps->ss.ss_currentRelation = NULL;
+		cps->ss.ss_currentScanDesc = NULL;
+		cps->ss.ss_ScanTupleSlot = NULL;
+		cps->ss.ps.ps_ProjInfo = NULL;
+	}
+	/*
+	 * Then, custom-plan provider can have all the own original
+	 * initialization on demand.
+	 */
+	cps->methods->BeginCustomPlan(cps, estate, eflags);
+
+	return cps;
+}
+
+TupleTableSlot *
+ExecCustomPlan(CustomPlanState *cpstate)
+{
+	Assert(cpstate->methods->ExecCustomPlan != NULL);
+	return cpstate->methods->ExecCustomPlan(cpstate);
+}
+
+Node *
+MultiExecCustomPlan(CustomPlanState *cpstate)
+{
+	if (!cpstate->methods->MultiExecCustomPlan)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("CustomPlan \"%s\" does not support MultiExec method",
+						cpstate->methods->CustomName)));
+	return cpstate->methods->MultiExecCustomPlan(cpstate);
+}
+
+void
+ExecEndCustomPlan(CustomPlanState *cpstate)
+{
+	Assert(cpstate->methods->EndCustomPlan != NULL);
+	cpstate->methods->EndCustomPlan(cpstate);
+
+	/* Free the exprcontext */
+	ExecFreeExprContext(&cpstate->ss.ps);
+
+	/* Clean out the tuple table */
+	ExecClearTuple(cpstate->ss.ps.ps_ResultTupleSlot);
+	if (cpstate->ss.ss_ScanTupleSlot)
+		ExecClearTuple(cpstate->ss.ss_ScanTupleSlot);
+
+	/* Close the heap relation, if needed */
+	if (cpstate->ss.ss_currentRelation)
+		ExecCloseScanRelation(cpstate->ss.ss_currentRelation);
+}
+
+void
+ExecReScanCustomPlan(CustomPlanState *cpstate)
+{
+	Assert(cpstate->methods->ReScanCustomPlan != NULL);
+	cpstate->methods->ReScanCustomPlan(cpstate);
+}
+
+void
+ExecCustomMarkPos(CustomPlanState *cpstate)
+{
+	if (!cpstate->methods->MarkPosCustomPlan)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+		errmsg("MarkPos is not supported by custom plan provider: %s",
+			   cpstate->methods->CustomName)));
+	cpstate->methods->MarkPosCustomPlan(cpstate);
+}
+
+void
+ExecCustomRestrPos(CustomPlanState *cpstate)
+{
+	if (!cpstate->methods->RestrPosCustomPlan)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+		errmsg("RestrPos is not supported by custom plan provider: %s",
+			   cpstate->methods->CustomName)));
+	cpstate->methods->RestrPosCustomPlan(cpstate);
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 8d3d5a7..784ab23 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -598,6 +598,21 @@ _copyForeignScan(const ForeignScan *from)
 }
 
 /*
+ * _copyCustomPlan
+ */
+static CustomPlan *
+_copyCustomPlan(const CustomPlan *from)
+{
+	CustomPlan *newnode;
+
+	newnode = from->methods->CopyCustomPlan(from);
+	Assert(nodeTag(newnode) == nodeTag(from));
+	CopyScanFields((const Scan *) from, (Scan *) newnode);
+
+	return newnode;
+}
+
+/*
  * CopyJoinFields
  *
  *		This function copies the fields of the Join node.  It is used by
@@ -3834,6 +3849,19 @@ _copyAlterTSConfigurationStmt(const AlterTSConfigurationStmt *from)
 	return newnode;
 }
 
+static CreateCustomPlanProviderStmt *
+_copyCreateCustomPlanProviderStmt(const CreateCustomPlanProviderStmt *from)
+{
+	CreateCustomPlanProviderStmt *newnode
+		= makeNode(CreateCustomPlanProviderStmt);
+
+	COPY_STRING_FIELD(cpp_name);
+	COPY_SCALAR_FIELD(cpp_class);
+	COPY_NODE_FIELD(cpp_options);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					pg_list.h copy functions
  * ****************************************************************
@@ -3997,6 +4025,10 @@ copyObject(const void *from)
 		case T_ForeignScan:
 			retval = _copyForeignScan(from);
 			break;
+		case T_CustomPlan:
+		case T_CustomPlanMarkPos:
+			retval = _copyCustomPlan(from);
+			break;
 		case T_Join:
 			retval = _copyJoin(from);
 			break;
@@ -4543,6 +4575,9 @@ copyObject(const void *from)
 		case T_AlterTSConfigurationStmt:
 			retval = _copyAlterTSConfigurationStmt(from);
 			break;
+		case T_CreateCustomPlanProviderStmt:
+			retval = _copyCreateCustomPlanProviderStmt(from);
+			break;
 
 		case T_A_Expr:
 			retval = _copyAExpr(from);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e7b49f6..fe96f9f 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1995,6 +1995,17 @@ _equalAlterTSConfigurationStmt(const AlterTSConfigurationStmt *a,
 }
 
 static bool
+_equalCreateCustomPlanProviderStmt(const CreateCustomPlanProviderStmt *a,
+								   const CreateCustomPlanProviderStmt *b)
+{
+	COMPARE_STRING_FIELD(cpp_name);
+	COMPARE_SCALAR_FIELD(cpp_class);
+	COMPARE_NODE_FIELD(cpp_options);
+
+	return true;
+}
+
+static bool
 _equalAExpr(const A_Expr *a, const A_Expr *b)
 {
 	COMPARE_SCALAR_FIELD(kind);
@@ -3009,6 +3020,9 @@ equal(const void *a, const void *b)
 		case T_AlterTSConfigurationStmt:
 			retval = _equalAlterTSConfigurationStmt(a, b);
 			break;
+		case T_CreateCustomPlanProviderStmt:
+			retval = _equalCreateCustomPlanProviderStmt(a, b);
+			break;
 
 		case T_A_Expr:
 			retval = _equalAExpr(a, b);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index c182212..ba462df 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -564,6 +564,22 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
 }
 
 static void
+_outCustomPlan(StringInfo str, const CustomPlan *node)
+{
+	if (IsA(node, CustomPlan))
+		WRITE_NODE_TYPE("CUSTOMPLAN");
+	else if (IsA(node, CustomPlanMarkPos))
+		WRITE_NODE_TYPE("CUSTOMPLANMARKPOS");
+	else
+		elog(ERROR, "unexpected node tag given: %d", (int)nodeTag(node));
+
+	_outScanInfo(str, (const Scan *) node);
+	appendStringInfo(str, " :methods");
+	_outToken(str, node->methods->CustomName);
+	node->methods->TextOutCustomPlan(str, node);
+}
+
+static void
 _outJoin(StringInfo str, const Join *node)
 {
 	WRITE_NODE_TYPE("JOIN");
@@ -1582,6 +1598,16 @@ _outForeignPath(StringInfo str, const ForeignPath *node)
 }
 
 static void
+_outCustomPath(StringInfo str, const CustomPath *node)
+{
+	WRITE_NODE_TYPE("CUSTOMPATH");
+	_outPathInfo(str, (const Path *) node);
+	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");
@@ -2832,6 +2858,10 @@ _outNode(StringInfo str, const void *obj)
 			case T_ForeignScan:
 				_outForeignScan(str, obj);
 				break;
+			case T_CustomPlan:
+			case T_CustomPlanMarkPos:
+				_outCustomPlan(str, obj);
+				break;
 			case T_Join:
 				_outJoin(str, obj);
 				break;
@@ -3040,6 +3070,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..dbba5ae 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -336,7 +336,7 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 				}
 				break;
 			case RTE_SUBQUERY:
-				/* Subquery --- fully handled during set_rel_size */
+				/* Subquery --- path was added during set_rel_size */
 				break;
 			case RTE_FUNCTION:
 				/* RangeFunction */
@@ -347,12 +347,17 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 				set_values_pathlist(root, rel, rte);
 				break;
 			case RTE_CTE:
-				/* CTE reference --- fully handled during set_rel_size */
+				/* CTE reference --- path was added during set_rel_size */
 				break;
 			default:
 				elog(ERROR, "unexpected rtekind: %d", (int) rel->rtekind);
 				break;
 		}
+		/* Also, consider paths by custom-plan providers */
+		call_custom_scan_providers(root, rel, rte);
+
+		/* Select cheapest path */
+		set_cheapest(rel);
 	}
 
 #ifdef OPTIMIZER_DEBUG
@@ -401,9 +406,6 @@ set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 
 	/* Consider TID scans */
 	create_tidscan_paths(root, rel);
-
-	/* Now find the cheapest of the paths for this rel */
-	set_cheapest(rel);
 }
 
 /*
@@ -429,9 +431,6 @@ set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 {
 	/* Call the FDW's GetForeignPaths function to generate path(s) */
 	rel->fdwroutine->GetForeignPaths(root, rel, rte->relid);
-
-	/* Select cheapest path */
-	set_cheapest(rel);
 }
 
 /*
@@ -1272,9 +1271,6 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
 
 	/* Generate appropriate path */
 	add_path(rel, create_subqueryscan_path(root, rel, pathkeys, required_outer));
-
-	/* Select cheapest path (pretty easy in this case...) */
-	set_cheapest(rel);
 }
 
 /*
@@ -1343,9 +1339,6 @@ set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 	/* Generate appropriate path */
 	add_path(rel, create_functionscan_path(root, rel,
 										   pathkeys, required_outer));
-
-	/* Select cheapest path (pretty easy in this case...) */
-	set_cheapest(rel);
 }
 
 /*
@@ -1366,9 +1359,6 @@ set_values_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 
 	/* Generate appropriate path */
 	add_path(rel, create_valuesscan_path(root, rel, required_outer));
-
-	/* Select cheapest path (pretty easy in this case...) */
-	set_cheapest(rel);
 }
 
 /*
@@ -1435,9 +1425,6 @@ set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 
 	/* Generate appropriate path */
 	add_path(rel, create_ctescan_path(root, rel, required_outer));
-
-	/* Select cheapest path (pretty easy in this case...) */
-	set_cheapest(rel);
 }
 
 /*
@@ -1488,9 +1475,6 @@ set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 
 	/* Generate appropriate path */
 	add_path(rel, create_worktablescan_path(root, rel, required_outer));
-
-	/* Select cheapest path (pretty easy in this case...) */
-	set_cheapest(rel);
 }
 
 /*
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 4b641a2..9939ad8 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -77,13 +77,13 @@ 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 Plan *create_custom_plan(PlannerInfo *root, CustomPath *best_path);
 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,
 					  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);
@@ -261,6 +261,9 @@ create_plan_recurse(PlannerInfo *root, Path *best_path)
 			plan = create_unique_plan(root,
 									  (UniquePath *) best_path);
 			break;
+		case T_CustomPlan:
+			plan = create_custom_plan(root, (CustomPath *) best_path);
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) best_path->pathtype);
@@ -1072,6 +1075,97 @@ 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 'scan_clauses' and targetlist 'tlist'.
+ */
+static Plan *
+create_custom_plan(PlannerInfo *root, CustomPath *best_path)
+{
+	CustomPlan	   *custom_plan;
+	RelOptInfo	   *rel;
+	List		   *tlist = NIL;
+	List		   *clauses = NIL;
+
+	/*
+	 * Create a custom-plan object delivered from CustomPlan type,
+	 * according to the supplied CustomPath
+	 */
+	Assert(best_path->path.pathtype == T_CustomPlan ||
+		   best_path->path.pathtype == T_CustomPlanMarkPos);
+	custom_plan = (CustomPlan *)
+		best_path->methods->CreateCustomPlan(root, best_path);
+	Assert(nodeTag(custom_plan) == best_path->path.pathtype);
+
+	rel = best_path->path.parent;
+	if (rel)
+	{
+		if (rel->reloptkind == RELOPT_BASEREL)
+		{
+			Assert(rel->relid > 0);
+			custom_plan->scan.scanrelid = rel->relid;
+
+			/*
+			 * For table scans, rather than using the relation targetlist
+			 * (which is only those Vars actually needed by the query),
+			 * we prefer to generate a tlist containing all Vars in order.
+			 * This will allow the executor to optimize away projection of
+			 * the table tuples, if possible.
+			 */
+			if (use_physical_tlist(root, rel))
+				tlist = build_physical_tlist(root, rel);
+		}
+		/* elsewhere, we generate a tlist from the relation targetlist */
+		if (tlist == NIL)
+			tlist = build_path_tlist(root, &best_path->path);
+
+		/*
+		 * Extract the relevant restriction clauses from the parent relation.
+		 * The executor must apply all these restrictions during the scan,
+		 * except for pseudoconstants which we'll take care of below.
+		 */
+		clauses = rel->baserestrictinfo;
+
+		/*
+		 * If this is a parameterized scan, we also need to enforce all
+		 * the join clauses available from the outer relation(s).
+		 */
+		if (best_path->path.param_info)
+			clauses = list_concat(list_copy(clauses),
+								  best_path->path.param_info->ppi_clauses);
+
+		/* Sort clauses into best execution order */
+		clauses = order_qual_clauses(root, clauses);
+
+		/*
+		 * Replace outer-relation variables with nestloop params.
+		 * Note that any other clauses which is managed by extension
+		 * itself has to be handled in InitCustomPlan() method, as
+		 * built-in code doing.
+		 */
+		if (best_path->path.param_info)
+			clauses = (List *)replace_nestloop_params(root, (Node *)clauses);
+	}
+	/*
+	 * Copy cost data from Path to Plan; no need to make custom-plan
+	 * providers do this
+	 */
+	copy_path_costsize((Plan *)custom_plan, (Path *)best_path);
+
+	/*
+	 * Let the custom-plan provider perform its final initialization
+	 * of this CustomPlan (to be an inherited type, actually) node
+	 * according to its own necessity.
+	 * Note that custom-plan provider may/can replace (or stack another
+	 * one on) its own custom-plan node on demand, for example, to add
+	 * Result node to handle pseudo constant using create_gating_plan().
+	 */
+	return custom_plan->methods->InitCustomPlan(custom_plan,
+												root, best_path,
+												tlist, clauses);
+}
 
 /*****************************************************************************
  *
@@ -2540,7 +2634,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 4d717df..c416859 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,14 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 			}
 			break;
 
+		case T_CustomPlan:
+			{
+				CustomPlan *cplan = (CustomPlan *) plan;
+
+				cplan->methods->SetCustomPlanRef(root, cplan, rtoffset);
+			}
+			break;
+
 		case T_NestLoop:
 		case T_MergeJoin:
 		case T_HashJoin:
@@ -1158,7 +1165,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..9833073 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -2283,6 +2283,30 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
 			context.paramids = bms_add_members(context.paramids, scan_params);
 			break;
 
+		case T_CustomPlan:
+			{
+				CustomPlan *cplan = (CustomPlan *) plan;
+
+				/*
+				 * If this custom-plan scab a particular relation, we
+				 * adjust paramids as other scan derivered node.
+				 */
+				if (cplan->scan.scanrelid > 0)
+					context.paramids = bms_add_members(context.paramids,
+													   scan_params);
+				/*
+				 * custom plan provider is responsible to apply
+				 * finalize_primnode() on the expression node of its
+				 * private fields, but no need to apply tlist and
+				 * qual of Plan node (already done above).
+				 */
+				if (cplan->methods->FinalizeCustomPlan)
+					cplan->methods->FinalizeCustomPlan(root, cplan,
+													   finalize_primnode,
+													   (void *)&context);
+			}
+			break;
+
 		case T_ModifyTable:
 			{
 				ModifyTable *mtplan = (ModifyTable *) plan;
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 4e05dcd..a1eadaf 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -16,6 +16,10 @@
 
 #include <math.h>
 
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "catalog/pg_custom_plan_provider.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
@@ -25,8 +29,11 @@
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
+#include "utils/inval.h"
 #include "utils/lsyscache.h"
+#include "utils/memutils.h"
 #include "utils/selfuncs.h"
+#include "utils/syscache.h"
 
 
 typedef enum
@@ -2067,3 +2074,106 @@ reparameterize_path(PlannerInfo *root, Path *path,
 	}
 	return NULL;
 }
+
+/*****************************************************************************
+ *		creation of custom-plan paths
+ *****************************************************************************/
+static List *custom_scan_callchain = NIL;
+static bool custom_plan_callchain_is_ready = false;
+static MemoryContext custom_plan_memcxt = NULL;
+
+static void
+invalidate_custom_plan_callchain(Datum arg, int cacheid, uint32 hashvalue)
+{
+	MemoryContextReset(custom_plan_memcxt);
+	custom_plan_callchain_is_ready = false;
+	custom_scan_callchain = NIL;
+}
+
+static void
+setup_custom_plan_callchain(void)
+{
+	Relation		rel;
+	SysScanDesc		scan;
+	HeapTuple		tuple;
+	MemoryContext	oldcxt;
+
+	custom_scan_callchain = NIL;
+
+	rel = heap_open(CustomPlanProviderRelationId, AccessShareLock);
+
+	/* full scan on the pg_custom_plan once */
+	scan = systable_beginscan(rel, InvalidOid, false, NULL, 0, NULL);
+
+	oldcxt = MemoryContextSwitchTo(custom_plan_memcxt);
+	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+	{
+		Form_pg_custom_plan_provider	cppForm
+			= (Form_pg_custom_plan_provider) GETSTRUCT(tuple);
+
+		if (cppForm->cppclass == CUSTOMPLAN_CLASS_SCAN)
+		{
+			custom_scan_callchain = lappend_oid(custom_scan_callchain,
+												cppForm->cpphandler);
+		}
+		else
+			elog(LOG, "Bug? custom-plan provider \"%s\" has unknown class: %c",
+				 NameStr(cppForm->cppname), cppForm->cppclass);
+	}
+	MemoryContextSwitchTo(oldcxt);
+	systable_endscan(scan);
+
+	heap_close(rel, AccessShareLock);
+
+	custom_plan_callchain_is_ready = true;
+}
+
+static void
+init_custom_plan_callchain(void)
+{
+	/* memory context to keep callchain for custom-plans */
+	custom_plan_memcxt = AllocSetContextCreate(CacheMemoryContext,
+											   "custom plan memory context",
+											   ALLOCSET_DEFAULT_MINSIZE,
+											   ALLOCSET_DEFAULT_INITSIZE,
+											   ALLOCSET_DEFAULT_MAXSIZE);
+
+	/* flush cached callchain on catalog updates */
+	CacheRegisterSyscacheCallback(CUSTOMPLANPROVIDEROID,
+								  invalidate_custom_plan_callchain,
+								  (Datum) 0);
+	/* also, initial setting up */
+	setup_custom_plan_callchain();
+}
+
+/*
+ * call_custom_scan_providers
+ *
+ * A callchain on relation scan. custom-plan provider can add alternative
+ * scan paths derivered from CustomPath class.
+ */
+void
+call_custom_scan_providers(PlannerInfo *root,
+						   RelOptInfo *baserel,
+						   RangeTblEntry *rte)
+{
+	customScanArg	sarg;
+	ListCell	   *cell;
+
+	if (!custom_plan_memcxt)
+		init_custom_plan_callchain();
+	else if (!custom_plan_callchain_is_ready)
+		setup_custom_plan_callchain();
+
+	Assert(custom_plan_callchain_is_ready);
+	sarg.custom_class = CUSTOMPLAN_CLASS_SCAN;
+	sarg.root = root;
+	sarg.baserel = baserel;
+	sarg.rte = rte;
+
+	foreach (cell, custom_scan_callchain)
+	{
+		(void) OidFunctionCall1(lfirst_oid(cell),
+								PointerGetDatum(&sarg));
+	}
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ba7d091..a799aed 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -51,6 +51,7 @@
 
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/pg_custom_plan_provider.h"
 #include "catalog/pg_trigger.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -251,6 +252,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		DropOwnedStmt ReassignOwnedStmt
 		AlterTSConfigurationStmt AlterTSDictionaryStmt
 		CreateMatViewStmt RefreshMatViewStmt
+		CreateCustomPlanProviderStmt DropCustomPlanProviderStmt
 
 %type <node>	select_no_parens select_with_parens select_clause
 				simple_select values_clause
@@ -504,6 +506,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
 
+%type <ival>	cpp_class
+%type <defelt>	cpp_option
+%type <list>	opt_cpp_options cpp_options
+
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -539,7 +545,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
 	CROSS CSV CURRENT_P
 	CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
-	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
+	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CUSTOM CYCLE
 
 	DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
 	DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DESC
@@ -578,8 +584,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	OBJECT_P OF OFF OFFSET OIDS ON ONLY OPERATOR OPTION OPTIONS OR
 	ORDER ORDINALITY OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER
 
-	PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POSITION
-	PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
+	PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLAN PLANS POSITION
+	PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY PROVIDER
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROGRAM
 
 	QUOTE
@@ -589,8 +595,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK
 	ROW ROWS RULE
 
-	SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
-	SERIALIZABLE SERVER SESSION SESSION_USER SET SETOF SHARE
+	SAVEPOINT SCAN SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE
+	SEQUENCES SERIALIZABLE SERVER SESSION SESSION_USER SET SETOF SHARE
 	SHOW SIMILAR SIMPLE SMALLINT SNAPSHOT SOME STABLE STANDALONE_P START
 	STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBSTRING
 	SYMMETRIC SYSID SYSTEM_P
@@ -752,6 +758,7 @@ stmt :
 			| CreateAssertStmt
 			| CreateCastStmt
 			| CreateConversionStmt
+			| CreateCustomPlanProviderStmt
 			| CreateDomainStmt
 			| CreateExtensionStmt
 			| CreateFdwStmt
@@ -782,6 +789,7 @@ stmt :
 			| DoStmt
 			| DropAssertStmt
 			| DropCastStmt
+			| DropCustomPlanProviderStmt
 			| DropFdwStmt
 			| DropForeignServerStmt
 			| DropGroupStmt
@@ -8648,6 +8656,78 @@ CreateConversionStmt:
 			}
 		;
 
+/****************************************************************************
+ *
+ *	QUERY:
+ *			CREATE CUSTOM PLAN PROVIDER name FOR <class> <options>
+ *
+ ****************************************************************************/
+
+CreateCustomPlanProviderStmt:
+			CREATE CUSTOM PLAN PROVIDER name FOR cpp_class opt_cpp_options
+			{
+				CreateCustomPlanProviderStmt *n
+					= makeNode(CreateCustomPlanProviderStmt);
+				n->cpp_name = $5;
+				n->cpp_class = $7;
+				n->cpp_options = $8;
+				$$ = (Node *) n;
+			}
+		;
+
+cpp_class:
+			SCAN				{ $$ = CUSTOMPLAN_CLASS_SCAN; }
+		;
+
+cpp_option:
+			HANDLER handler_name
+			{
+				$$ = makeDefElem("handler", (Node *)$2);
+			}
+		;
+
+cpp_options:
+			cpp_option					{ $$ = list_make1($1); }
+			| cpp_options cpp_option	{ $$ = lappend($1, $2); }
+		;
+
+opt_cpp_options:
+			cpp_options			{ $$ = $1; }
+			| /* empty */		{ $$ = NIL; }
+		;
+
+/****************************************************************************
+ *
+ *     QUERY:
+ *             DROP CUSTOM PLAN PROVIDER name
+ *
+ ****************************************************************************/
+
+DropCustomPlanProviderStmt:
+			DROP CUSTOM PLAN PROVIDER name opt_drop_behavior
+			{
+				DropStmt *n = makeNode(DropStmt);
+				n->removeType = OBJECT_CPP;
+				n->objects = list_make1(list_make1(makeString($5)));
+				n->arguments = NIL;
+				n->missing_ok = false;
+				n->behavior = $6;
+				n->concurrent = false;
+				$$ = (Node *) n;
+			}
+		|	DROP CUSTOM PLAN PROVIDER IF_P EXISTS name opt_drop_behavior
+			{
+				DropStmt *n = makeNode(DropStmt);
+				n->removeType = OBJECT_CPP;
+				n->objects = list_make1(list_make1(makeString($7)));
+				n->arguments = NIL;
+				n->missing_ok = true;
+				n->behavior = $8;
+				n->concurrent = false;
+				$$ = (Node *) n;
+			}
+		;
+
 /*****************************************************************************
  *
  *		QUERY:
@@ -12857,6 +12937,7 @@ unreserved_keyword:
 			| CSV
 			| CURRENT_P
 			| CURSOR
+			| CUSTOM
 			| CYCLE
 			| DATA_P
 			| DATABASE
@@ -12967,6 +13048,7 @@ unreserved_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PLAN
 			| PLANS
 			| PRECEDING
 			| PREPARE
@@ -12977,6 +13059,7 @@ unreserved_keyword:
 			| PROCEDURAL
 			| PROCEDURE
 			| PROGRAM
+			| PROVIDER
 			| QUOTE
 			| RANGE
 			| READ
@@ -13002,6 +13085,7 @@ unreserved_keyword:
 			| ROWS
 			| RULE
 			| SAVEPOINT
+			| SCAN
 			| SCHEMA
 			| SCROLL
 			| SEARCH
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 3423898..39cebd9 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -202,6 +202,7 @@ check_xact_readonly(Node *parsetree)
 		case T_AlterTableSpaceOptionsStmt:
 		case T_AlterTableSpaceMoveStmt:
 		case T_CreateForeignTableStmt:
+		case T_CreateCustomPlanProviderStmt:
 		case T_SecLabelStmt:
 			PreventCommandIfReadOnly(CreateCommandTag(parsetree));
 			break;
@@ -683,6 +684,14 @@ standard_ProcessUtility(Node *parsetree,
 			AlterEventTrigger((AlterEventTrigStmt *) parsetree);
 			break;
 
+		case  T_CreateCustomPlanProviderStmt:
+			{
+				CreateCustomPlanProviderStmt *cpp_stmt
+					= (CreateCustomPlanProviderStmt *)parsetree;
+				DefineCustomPlanProvider(cpp_stmt);
+			}
+			break;
+
 			/*
 			 * ******************************** ROLE statements ****
 			 */
@@ -1940,6 +1949,9 @@ CreateCommandTag(Node *parsetree)
 				case OBJECT_OPFAMILY:
 					tag = "DROP OPERATOR FAMILY";
 					break;
+				case OBJECT_CPP:
+					tag = "DROP CUSTOM PLAN PROVIDER";
+					break;
 				default:
 					tag = "???";
 			}
@@ -2207,6 +2219,10 @@ CreateCommandTag(Node *parsetree)
 			tag = "ALTER EVENT TRIGGER";
 			break;
 
+		case T_CreateCustomPlanProviderStmt:
+			tag = "CREATE CUSTOM PLAN PROVIDER";
+			break;
+
 		case T_CreatePLangStmt:
 			tag = "CREATE LANGUAGE";
 			break;
@@ -2830,6 +2846,10 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_CreateCustomPlanProviderStmt:
+			lev = LOGSTMT_DDL;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 0781ac8..abe27e2 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -5481,6 +5481,29 @@ 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-plan provider replaced a varno in expression tree by special
+ * varno.
+ */
+static Node *
+GetSpecialCustomVar(PlanState *ps, Var *varnode, PlanState **child_ps)
+{
+	CustomPlanState *cps = (CustomPlanState *) ps;
+
+	Assert(IsA(ps, CustomPlanState));
+	Assert(IS_SPECIAL_VARNO(varnode->varno));
+
+	if (!cps->methods->GetSpecialCustomVar)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("%s does not support special varno reference",
+						cps->methods->CustomName)));
+	return (Node *)cps->methods->GetSpecialCustomVar(cps, varnode, child_ps);
+}
 
 /*
  * Display a Var appropriately.
@@ -5510,6 +5533,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;
 
@@ -5534,6 +5559,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, CustomPlanState) &&
+			 (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;
@@ -5748,6 +5796,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;
 
@@ -5822,6 +5871,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, CustomPlanState) &&
+			 (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/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 94d951c..1619de7 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -32,6 +32,7 @@
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_conversion.h"
+#include "catalog/pg_custom_plan_provider.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_db_role_setting.h"
 #include "catalog/pg_default_acl.h"
@@ -345,6 +346,28 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		8
 	},
+	{CustomPlanProviderRelationId,	/* CUSTOMPLANPROVIDEROID */
+		CustomPlanProviderOidIndexId,
+		1,
+		{
+			ObjectIdAttributeNumber,
+			0,
+			0,
+			0
+		},
+		32
+	},
+	{CustomPlanProviderRelationId,	/* CUSTOMPLANPROVIDERNAME */
+		CustomPlanProviderNameIndexId,
+		1,
+		{
+			Anum_pg_custom_plan_provider_cppname,
+			0,
+			0,
+			0,
+		},
+		32
+	},
 	{DatabaseRelationId,		/* DATABASEOID */
 		DatabaseOidIndexId,
 		1,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 8ed2592..4a7186a 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -147,6 +147,7 @@ typedef enum ObjectClass
 	OCLASS_DEFACL,				/* pg_default_acl */
 	OCLASS_EXTENSION,			/* pg_extension */
 	OCLASS_EVENT_TRIGGER,		/* pg_event_trigger */
+	OCLASS_CPP,					/* pg_custom_plan_provider */
 	MAX_OCLASS					/* MUST BE LAST */
 } ObjectClass;
 
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 59576fd..b926e15 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -299,6 +299,12 @@ DECLARE_UNIQUE_INDEX(pg_extension_name_index, 3081, on pg_extension using btree(
 DECLARE_UNIQUE_INDEX(pg_range_rngtypid_index, 3542, on pg_range using btree(rngtypid oid_ops));
 #define RangeTypidIndexId					3542
 
+DECLARE_UNIQUE_INDEX(pg_custom_plan_provider_oid_index, 3563, on pg_custom_plan_provider using btree(oid oid_ops));
+#define CustomPlanProviderOidIndexId 3563
+
+DECLARE_UNIQUE_INDEX(pg_custom_plan_provider_name_index, 3564, on pg_custom_plan_provider using btree(cppname name_ops));
+#define CustomPlanProviderNameIndexId 3564
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_custom_plan_provider.h b/src/include/catalog/pg_custom_plan_provider.h
new file mode 100644
index 0000000..09d4573
--- /dev/null
+++ b/src/include/catalog/pg_custom_plan_provider.h
@@ -0,0 +1,50 @@
+/* -------------------------------------------------------------------------
+ *
+ * pg_custom_plan_provider.h
+ *	definition of the system "custom plan provider" relation
+ *  (pg_custom_plan_provider)
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * -------------------------------------------------------------------------
+ */
+#ifndef PG_CUSTOM_PLAN_PROVIDER_H
+#define PG_CUSTOM_PLAN_PROVIDER_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *     pg_custom_plan definition.  cpp turns this into
+ *     typedef struct FormData_pg_custom_plan
+ * ----------------
+ */
+#define CustomPlanProviderRelationId       3562
+
+CATALOG(pg_custom_plan_provider,3562)
+{
+	NameData	cppname;		/* name of custom-plan provider */
+	char		cppclass;		/* class of custom-plan */
+	Oid			cpphandler;		/* function of custom-plan provider */
+} FormData_pg_custom_plan_provider;
+
+/* ----------------
+ *     Form_pg_custom_plan_provider corresponds to a pointer to a tuple
+ *     with the format of pg_custom_plan_provider relation.
+ * ----------------
+ */
+typedef FormData_pg_custom_plan_provider *Form_pg_custom_plan_provider;
+
+/* ----------------
+ *     compiler constants for pg_custom_plan_provider
+ * ----------------
+ */
+#define Natts_pg_custom_plan_provider			3
+#define Anum_pg_custom_plan_provider_cppname	1
+#define Anum_pg_custom_plan_provider_cppclass	2
+#define Anum_pg_custom_plan_provider_cpphandler	3
+
+/* definition of custclass */
+#define CUSTOMPLAN_CLASS_SCAN			's'
+
+#endif	/* PG_CUSTOM_PLAN_PROVIDER_H */
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index f8b4a65..fc798a2 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");
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 5ec9374..82d5d06 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -129,6 +129,11 @@ extern Datum transformGenericOptions(Oid catalogId,
 						List *options,
 						Oid fdwvalidator);
 
+/* commands/custom_plan.c */
+extern Oid get_custom_plan_provider_oid(const char *cpp_name, bool missing_ok);
+extern void	RemoveCustomPlanProviderById(Oid cust_oid);
+extern Oid	DefineCustomPlanProvider(CreateCustomPlanProviderStmt *stmt);
+
 /* support routines in commands/define.c */
 
 extern char *defGetString(DefElem *def);
diff --git a/src/include/executor/nodeCustom.h b/src/include/executor/nodeCustom.h
new file mode 100644
index 0000000..abe1e94
--- /dev/null
+++ b/src/include/executor/nodeCustom.h
@@ -0,0 +1,30 @@
+/* ------------------------------------------------------------------------
+ *
+ * nodeCustom.h
+ *
+ * prototypes for CustomPlan 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 CustomPlanState *ExecInitCustomPlan(CustomPlan *cplan,
+										   EState *estate, int eflags);
+extern TupleTableSlot *ExecCustomPlan(CustomPlanState *node);
+extern Node *MultiExecCustomPlan(CustomPlanState *node);
+extern void ExecEndCustomPlan(CustomPlanState *node);
+
+extern void ExecReScanCustomPlan(CustomPlanState *node);
+extern void ExecCustomMarkPos(CustomPlanState *node);
+extern void ExecCustomRestrPos(CustomPlanState *node);
+
+#endif	/* NODECUSTOM_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index b271f21..26e6b33 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1504,6 +1504,48 @@ typedef struct ForeignScanState
 	void	   *fdw_state;		/* foreign-data wrapper can keep state here */
 } ForeignScanState;
 
+/* ----------------
+ * CustomPlanState information
+ *
+ *		CustomPlan nodes are used to execute custom code within executor.
+ * ----------------
+ */
+struct CustomExecMethods;
+struct ExplainState;	/* to avoid to include explain.h here */
+
+typedef struct CustomPlanState
+{
+	ScanState	ss;
+	const struct CustomExecMethods *methods;
+} CustomPlanState;
+
+typedef struct CustomExecMethods
+{
+	const char     *CustomName;
+
+	/* EXECUTOR methods */
+	void    (*BeginCustomPlan)(CustomPlanState *node,
+							   EState *estate,
+							   int eflags);
+	TupleTableSlot *(*ExecCustomPlan)(CustomPlanState *node);
+	Node   *(*MultiExecCustomPlan)(CustomPlanState *node);
+	void	(*EndCustomPlan)(CustomPlanState *node);
+	void	(*ReScanCustomPlan)(CustomPlanState *node);
+	void	(*MarkPosCustomPlan)(CustomPlanState *node);
+	void	(*RestrPosCustomPlan)(CustomPlanState *node);
+
+	/* EXPLAIN support */
+	void	(*ExplainCustomPlanTargetRel)(CustomPlanState *node,
+										  struct ExplainState *es);
+	void    (*ExplainCustomPlan)(CustomPlanState *node,
+								 List *ancestors,
+								 struct ExplainState *es);
+	void	(*ExplainCustomPreScanNode)(CustomPlanState *node,
+										Bitmapset **rels_used);
+	Node   *(*GetSpecialCustomVar)(CustomPlanState *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 7b0088f..0fc7f75 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -62,6 +62,8 @@ typedef enum NodeTag
 	T_CteScan,
 	T_WorkTableScan,
 	T_ForeignScan,
+	T_CustomPlan,
+	T_CustomPlanMarkPos,
 	T_Join,
 	T_NestLoop,
 	T_MergeJoin,
@@ -107,6 +109,7 @@ typedef enum NodeTag
 	T_CteScanState,
 	T_WorkTableScanState,
 	T_ForeignScanState,
+	T_CustomPlanState,
 	T_JoinState,
 	T_NestLoopState,
 	T_MergeJoinState,
@@ -224,6 +227,7 @@ typedef enum NodeTag
 	T_HashPath,
 	T_TidPath,
 	T_ForeignPath,
+	T_CustomPath,
 	T_AppendPath,
 	T_MergeAppendPath,
 	T_ResultPath,
@@ -365,6 +369,7 @@ typedef enum NodeTag
 	T_RefreshMatViewStmt,
 	T_ReplicaIdentityStmt,
 	T_AlterSystemStmt,
+	T_CreateCustomPlanProviderStmt,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ff126eb..9711665 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1209,6 +1209,7 @@ typedef enum ObjectType
 	OBJECT_CONSTRAINT,
 	OBJECT_COLLATION,
 	OBJECT_CONVERSION,
+	OBJECT_CPP,
 	OBJECT_DATABASE,
 	OBJECT_DOMAIN,
 	OBJECT_EVENT_TRIGGER,
@@ -2050,6 +2051,18 @@ typedef struct AlterOpFamilyStmt
 } AlterOpFamilyStmt;
 
 /* ----------------------
+ *     Create Custom Plan Provider Statement
+ * ----------------------
+ */
+typedef struct CreateCustomPlanProviderStmt
+{
+	NodeTag     type;
+	char	   *cpp_name;		/* name of custom-plan provider */
+	char		cpp_class;		/* class of custom-plan provides */
+	List	   *cpp_options;	/* generic options for provider */
+} CreateCustomPlanProviderStmt;
+
+/* ----------------------
  *		Drop Table|Sequence|View|Index|Type|Domain|Conversion|Schema Statement
  * ----------------------
  */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 3b9c683..b48c735 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -15,6 +15,7 @@
 #define PLANNODES_H
 
 #include "access/sdir.h"
+#include "lib/stringinfo.h"
 #include "nodes/bitmapset.h"
 #include "nodes/primnodes.h"
 
@@ -479,6 +480,42 @@ typedef struct ForeignScan
 	bool		fsSystemCol;	/* true if any "system column" is needed */
 } ForeignScan;
 
+/* ----------------
+ *     CustomPlan node
+ * ----------------
+ */
+struct CustomPlanMethods;
+
+struct CustomPath;			/* to avoid to include nodes/relation.h here */
+struct PlannerInfo;			/* to avoid to include nodes/relation.h here */
+
+typedef struct CustomPlan
+{
+	Scan		scan;
+	const struct CustomPlanMethods *methods;
+} CustomPlan;
+
+typedef struct CustomPlanMethods
+{
+	const char *CustomName;
+	Plan	  *(*InitCustomPlan)(CustomPlan *custom_plan,
+								 struct PlannerInfo *root,
+								 struct CustomPath *best_path,
+								 List *tlist,
+								 List *clauses);
+	void	   (*SetCustomPlanRef)(struct PlannerInfo *root,
+								   CustomPlan *custom_plan,
+								   int rtoffset);
+	bool	   (*SupportBackwardScan)(CustomPlan *custom_plan);
+	void	   (*FinalizeCustomPlan)(struct PlannerInfo *root,
+									 CustomPlan *custom_plan,
+									 bool (*finalize_primnode)(),
+									 void *finalize_context);
+	Node	  *(*CreateCustomPlanState)(CustomPlan *custom_plan);
+	void	   (*TextOutCustomPlan)(StringInfo str,
+									const CustomPlan *node);
+	CustomPlan *(*CopyCustomPlan)(const CustomPlan *from);
+} CustomPlanMethods;
 
 /*
  * ==========
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index dacbe9c..a65bb0c 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"
@@ -881,6 +882,31 @@ 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;
+
+typedef struct CustomPath
+{
+	Path        path;
+	const struct CustomPathMethods *methods;
+} CustomPath;
+
+typedef struct CustomPathMethods
+{
+	const char *CustomName;
+	Node   *(*CreateCustomPlan)(PlannerInfo *root,
+								CustomPath *best_path);
+	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..df4d6e8 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -129,6 +129,20 @@ extern Path *reparameterize_path(PlannerInfo *root, Path *path,
 					double loop_count);
 
 /*
+ * interface towards custom-plan provider functions
+ */
+typedef struct {
+	uint32			custom_class;
+	PlannerInfo	   *root;
+	RelOptInfo	   *baserel;
+	RangeTblEntry  *rte;
+} customScanArg;
+
+extern void call_custom_scan_providers(PlannerInfo *root,
+									   RelOptInfo *baserel,
+									   RangeTblEntry *rte);
+
+/*
  * prototypes for relnode.c
  */
 extern void setup_simple_rel_arrays(PlannerInfo *root);
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 8bdb7db..76e3c86 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
@@ -128,6 +129,7 @@ extern List *remove_useless_joins(PlannerInfo *root, List *joinlist);
  */
 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 set_opfuncid(OpExpr *opexpr);
 extern void set_sa_opfuncid(ScalarArrayOpExpr *opexpr);
 extern void record_plan_function_dependency(PlannerInfo *root, Oid funcid);
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 04e9810..203755e 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -107,6 +107,7 @@ PG_KEYWORD("current_time", CURRENT_TIME, RESERVED_KEYWORD)
 PG_KEYWORD("current_timestamp", CURRENT_TIMESTAMP, RESERVED_KEYWORD)
 PG_KEYWORD("current_user", CURRENT_USER, RESERVED_KEYWORD)
 PG_KEYWORD("cursor", CURSOR, UNRESERVED_KEYWORD)
+PG_KEYWORD("custom", CUSTOM, UNRESERVED_KEYWORD)
 PG_KEYWORD("cycle", CYCLE, UNRESERVED_KEYWORD)
 PG_KEYWORD("data", DATA_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("database", DATABASE, UNRESERVED_KEYWORD)
@@ -281,6 +282,7 @@ PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD)
 PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD)
+PG_KEYWORD("plan", PLAN, UNRESERVED_KEYWORD)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD)
 PG_KEYWORD("position", POSITION, COL_NAME_KEYWORD)
 PG_KEYWORD("preceding", PRECEDING, UNRESERVED_KEYWORD)
@@ -294,6 +296,7 @@ PG_KEYWORD("privileges", PRIVILEGES, UNRESERVED_KEYWORD)
 PG_KEYWORD("procedural", PROCEDURAL, UNRESERVED_KEYWORD)
 PG_KEYWORD("procedure", PROCEDURE, UNRESERVED_KEYWORD)
 PG_KEYWORD("program", PROGRAM, UNRESERVED_KEYWORD)
+PG_KEYWORD("provider", PROVIDER, UNRESERVED_KEYWORD)
 PG_KEYWORD("quote", QUOTE, UNRESERVED_KEYWORD)
 PG_KEYWORD("range", RANGE, UNRESERVED_KEYWORD)
 PG_KEYWORD("read", READ, UNRESERVED_KEYWORD)
@@ -324,6 +327,7 @@ PG_KEYWORD("row", ROW, COL_NAME_KEYWORD)
 PG_KEYWORD("rows", ROWS, UNRESERVED_KEYWORD)
 PG_KEYWORD("rule", RULE, UNRESERVED_KEYWORD)
 PG_KEYWORD("savepoint", SAVEPOINT, UNRESERVED_KEYWORD)
+PG_KEYWORD("scan", SCAN, UNRESERVED_KEYWORD)
 PG_KEYWORD("schema", SCHEMA, UNRESERVED_KEYWORD)
 PG_KEYWORD("scroll", SCROLL, UNRESERVED_KEYWORD)
 PG_KEYWORD("search", SEARCH, UNRESERVED_KEYWORD)
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index f97229f..e1e56f7 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -52,6 +52,8 @@ enum SysCacheIdentifier
 	CONNAMENSP,
 	CONSTROID,
 	CONVOID,
+	CUSTOMPLANPROVIDEROID,
+	CUSTOMPLANPROVIDERNAME,
 	DATABASEOID,
 	DEFACLROLENSPOBJ,
 	ENUMOID,
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 111d24c..e3cad45 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -97,6 +97,7 @@ pg_class|t
 pg_collation|t
 pg_constraint|t
 pg_conversion|t
+pg_custom_plan|t
 pg_database|t
 pg_db_role_setting|t
 pg_default_acl|t
#7Shigeru Hanada
shigeru.hanada@gmail.com
In reply to: Kouhei Kaigai (#6)
1 attachment(s)

Kaigai-san,

The v3 patch had conflict in src/backend/tcop/utility.c for newly
added IMPORT FOREIGN SCHEMA statement, but it was trivial.

2014-07-08 20:55 GMT+09:00 Kouhei Kaigai <kaigai@ak.jp.nec.com>:

* System catalog name was changed to pg_custom_plan_provider;
that reflects role of the object being defined.

ISTM that doc/src/sgml/custom-plan.sgml should be also renamed to
custom-plan-provider.sgml.

* Also, prefix of its variable names are changed to "cpp"; that
means custom-plan-provider.

A "custclass" remains in a comment in
src/include/catalog/pg_custom_plan_provider.h.

* Syntax also reflects what the command does more. New syntax to
define custom plan provider is:
CREATE CUSTOM PLAN PROVIDER <cpp_name>
FOR <cpp_class> HANDLER <cpp_function>;
* Add custom-plan.sgml to introduce interface functions defined
for path/plan/exec methods.
* FinalizeCustomPlan() callback was simplified to support scan
(and join in the future) at the starting point. As long as
scan/join requirement, no need to control paramids bitmap in
arbitrary way.
* Unnecessary forward declaration in relation.h and plannode.h
were removed, but a few structures still needs to have
forward declarations.
* Fix typos being pointed out.

Check. I found some typos and a wording "datatype" which is not used
in any other place. Please refer the attached patch.

--
Shigeru HANADA

Attachments:

fix_typo_in_v3.patchapplication/octet-stream; name=fix_typo_in_v3.patchDownload
commit a2c9a2216b301d15bca91e1aaedbb09f0c7fabaf
Author: Shigeru HANADA <shigeru.hanada@gmail.com>
Date:   Wed Jul 9 17:32:30 2014 +0900

    Fix typos.

diff --git a/contrib/ctidscan/ctidscan.c b/contrib/ctidscan/ctidscan.c
index 327a6d2..5151365 100644
--- a/contrib/ctidscan/ctidscan.c
+++ b/contrib/ctidscan/ctidscan.c
@@ -270,7 +270,7 @@ CTidEstimateCosts(PlannerInfo *root,
 	{
 		/*
 		 * Just a rough estimation. We assume half of records shall be
-		 * read using this restriction clause, but undeterministic untill
+		 * read using this restriction clause, but indeterministic until
 		 * executor run it actually.
 		 */
 		num_pages = Max((baserel->pages + 1) / 2, 1);
@@ -491,7 +491,7 @@ CopyCtidScanPlan(const CustomPlan *from)
 
 /*
  * BeginCtidScan - A method of CustomPlanState; that initializes
- * the supplied CtidScanState object, at begining of the executor.
+ * the supplied CtidScanState object, at beginning of the executor.
  */
 static void
 BeginCtidScan(CustomPlanState *node, EState *estate, int eflags)
diff --git a/doc/src/sgml/custom-plan.sgml b/doc/src/sgml/custom-plan.sgml
index 320e5de..d86495b 100644
--- a/doc/src/sgml/custom-plan.sgml
+++ b/doc/src/sgml/custom-plan.sgml
@@ -38,8 +38,8 @@
   </para>
   <para>
    Handler function has to be declared as a function that takes one
-   <literal>internal</> datatype and returns <literal>void</>.
-   On invocation, query planner derivers a pointer of data structure
+   <literal>internal</> data type and returns <literal>void</>.
+   On invocation, query planner delivers a pointer of data structure
    according to the custom plan class, custom-plan handler function will
    decide whether it can offer alternative execution path towards the
    required task. If available, it can add a <literal>CustomPath</>
@@ -74,8 +74,8 @@
   <para>
    A handler function that was specified on
    the <xref linkend="sql-createcustomplanprovider"> command is
-   declared to return <literal>void</> datatype and takes an
-   <literal>internal</> datatype; that is usually applied to exchange
+   declared to return <literal>void</> data type and takes an
+   <literal>internal</> data type; that is usually applied to exchange
    internal data structure.
    This handler function is not an exception. It can reference a pointer
    being informed via function argument to understand the context.
@@ -104,7 +104,7 @@ typedef struct {
    The custom-plan provider being invoked can check whether it can
    provides alternative way to scan the relation. If available, it
    shall construct <literal>CustomPath</> or its inherited one with
-   estimated cost and callbacks below, then reguster the path using
+   estimated cost and callbacks below, then register the path using
    <literal>add_path</> towards the supplied <literal>RelOptInfo</>.
   </para>
  </sect1>
@@ -121,7 +121,7 @@ Node *
 CreateCustomPlan(PlannerInfo *root,
                  CustomPath *best_path);
 </programlisting>
-   It populates a <literal>CustomPlan</> (or inherited datatype) node
+   It populates a <literal>CustomPlan</> (or inherited data type) node
    according to the supplied <literal>CustomPath</> node which was
    constructed on the custom plan handler function then chosen by the
    query planner.
@@ -224,7 +224,7 @@ FinalizeCustomPlan(PlannerInfo *root,
 Node *
 CreateCustomPlanState(CustomPlan *custom_plan);
 </programlisting>
-   It populates a <literal>CustomPlanState</> (or inherited datatype)
+   It populates a <literal>CustomPlanState</> (or inherited data type)
    node according to the supplied <literal>CustomPlan</> node preliminary
    constructed, on the beginning of query executor.
    Only custom plan provider can know exact size of the node to be
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 4b2e57e..d6beb53 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -2161,7 +2161,7 @@ init_custom_plan_callchain(void)
  * call_custom_scan_providers
  *
  * A callchain on relation scan. custom-plan provider can add alternative
- * scan paths derivered from CustomPath class.
+ * scan paths derived from CustomPath class.
  */
 void
 call_custom_scan_providers(PlannerInfo *root,
#8Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Shigeru Hanada (#7)
1 attachment(s)

Hanada-san,

Thanks for your checking. The attached v4 patch is rebased one on the
latest master branch. Indeed, merge conflict was trivial.

Updates from the v3 are below:
- custom-plan.sgml was renamed to custom-plan-provider.sgml
- fix up the comments in pg_custom_plan_provider.h that mentioned
about old field name.
- applied your patch to fix up typos. (thanks so much!)
- put "costs off" on the EXPLAIN command in the regression test of
ctidscan extension.

Nothing to comment on the design and implementation from your
viewpoint any more?

Thanks,

2014-07-14 19:07 GMT+09:00 Shigeru Hanada <shigeru.hanada@gmail.com>:

Kaigai-san,

The v3 patch had conflict in src/backend/tcop/utility.c for newly
added IMPORT FOREIGN SCHEMA statement, but it was trivial.

2014-07-08 20:55 GMT+09:00 Kouhei Kaigai <kaigai@ak.jp.nec.com>:

* System catalog name was changed to pg_custom_plan_provider;
that reflects role of the object being defined.

ISTM that doc/src/sgml/custom-plan.sgml should be also renamed to
custom-plan-provider.sgml.

* Also, prefix of its variable names are changed to "cpp"; that
means custom-plan-provider.

A "custclass" remains in a comment in
src/include/catalog/pg_custom_plan_provider.h.

* Syntax also reflects what the command does more. New syntax to
define custom plan provider is:
CREATE CUSTOM PLAN PROVIDER <cpp_name>
FOR <cpp_class> HANDLER <cpp_function>;
* Add custom-plan.sgml to introduce interface functions defined
for path/plan/exec methods.
* FinalizeCustomPlan() callback was simplified to support scan
(and join in the future) at the starting point. As long as
scan/join requirement, no need to control paramids bitmap in
arbitrary way.
* Unnecessary forward declaration in relation.h and plannode.h
were removed, but a few structures still needs to have
forward declarations.
* Fix typos being pointed out.

Check. I found some typos and a wording "datatype" which is not used
in any other place. Please refer the attached patch.

--
Shigeru HANADA

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

Attachments:

pgsql-v9.5-custom-plan.v4.patchapplication/octet-stream; name=pgsql-v9.5-custom-plan.v4.patchDownload
 contrib/Makefile                                  |   1 +
 contrib/ctidscan/Makefile                         |  19 +
 contrib/ctidscan/ctidscan--1.0.sql                |  12 +
 contrib/ctidscan/ctidscan--unpackaged-1.0.sql     |   0
 contrib/ctidscan/ctidscan.c                       | 952 ++++++++++++++++++++++
 contrib/ctidscan/ctidscan.control                 |   5 +
 contrib/ctidscan/expected/ctidscan.out            | 312 +++++++
 contrib/ctidscan/sql/ctidscan.sql                 |  54 ++
 doc/src/sgml/catalogs.sgml                        |  59 ++
 doc/src/sgml/custom-plan-provider.sgml            | 431 ++++++++++
 doc/src/sgml/filelist.sgml                        |   1 +
 doc/src/sgml/postgres.sgml                        |   1 +
 doc/src/sgml/ref/allfiles.sgml                    |   2 +
 doc/src/sgml/ref/create_custom_plan_provider.sgml | 139 ++++
 doc/src/sgml/ref/drop_custom_plan_provider.sgml   | 108 +++
 doc/src/sgml/reference.sgml                       |   2 +
 src/backend/catalog/Makefile                      |   2 +-
 src/backend/catalog/dependency.c                  |  11 +-
 src/backend/catalog/objectaddress.c               |  63 ++
 src/backend/commands/Makefile                     |   2 +-
 src/backend/commands/custom_plan.c                | 188 +++++
 src/backend/commands/dropcmds.c                   |   5 +
 src/backend/commands/event_trigger.c              |   2 +
 src/backend/commands/explain.c                    |  40 +
 src/backend/executor/Makefile                     |   2 +-
 src/backend/executor/execAmi.c                    |  25 +
 src/backend/executor/execProcnode.c               |  19 +
 src/backend/executor/nodeCustom.c                 | 147 ++++
 src/backend/nodes/copyfuncs.c                     |  35 +
 src/backend/nodes/equalfuncs.c                    |  14 +
 src/backend/nodes/outfuncs.c                      |  33 +
 src/backend/optimizer/path/allpaths.c             |  30 +-
 src/backend/optimizer/plan/createplan.c           |  98 ++-
 src/backend/optimizer/plan/setrefs.c              |  11 +-
 src/backend/optimizer/plan/subselect.c            |  24 +
 src/backend/optimizer/util/pathnode.c             | 110 +++
 src/backend/parser/gram.y                         |  94 ++-
 src/backend/tcop/utility.c                        |  20 +
 src/backend/utils/adt/ruleutils.c                 |  73 ++
 src/backend/utils/cache/syscache.c                |  23 +
 src/include/catalog/dependency.h                  |   1 +
 src/include/catalog/indexing.h                    |   6 +
 src/include/catalog/pg_custom_plan_provider.h     |  50 ++
 src/include/catalog/pg_operator.h                 |   3 +
 src/include/commands/defrem.h                     |   5 +
 src/include/executor/nodeCustom.h                 |  30 +
 src/include/nodes/execnodes.h                     |  42 +
 src/include/nodes/nodes.h                         |   5 +
 src/include/nodes/parsenodes.h                    |  13 +
 src/include/nodes/plannodes.h                     |  37 +
 src/include/nodes/relation.h                      |  26 +
 src/include/optimizer/pathnode.h                  |  14 +
 src/include/optimizer/planmain.h                  |   2 +
 src/include/parser/kwlist.h                       |   4 +
 src/include/utils/syscache.h                      |   2 +
 src/test/regress/expected/sanity_check.out        |   1 +
 56 files changed, 3374 insertions(+), 36 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..1e476a6
--- /dev/null
+++ b/contrib/ctidscan/Makefile
@@ -0,0 +1,19 @@
+# contrib/ctidscan/Makefile
+
+MODULES = ctidscan
+
+EXTENSION = ctidscan
+DATA = ctidscan--1.0.sql
+
+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--1.0.sql b/contrib/ctidscan/ctidscan--1.0.sql
new file mode 100644
index 0000000..46420a0
--- /dev/null
+++ b/contrib/ctidscan/ctidscan--1.0.sql
@@ -0,0 +1,12 @@
+--
+-- Create ctidscan handler function
+--
+CREATE FUNCTION ctidscanaddpath(internal)
+  RETURNS pg_catalog.void
+  AS 'MODULE_PATHNAME','CtidScanAddPath'
+  LANGUAGE C STRICT;
+
+--
+-- Create a custom-plan provider
+--
+CREATE CUSTOM PLAN PROVIDER ctidscan FOR scan HANDLER ctidscanaddpath;
diff --git a/contrib/ctidscan/ctidscan--unpackaged-1.0.sql b/contrib/ctidscan/ctidscan--unpackaged-1.0.sql
new file mode 100644
index 0000000..e69de29
diff --git a/contrib/ctidscan/ctidscan.c b/contrib/ctidscan/ctidscan.c
new file mode 100644
index 0000000..5151365
--- /dev/null
+++ b/contrib/ctidscan/ctidscan.c
@@ -0,0 +1,952 @@
+/*
+ * 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_custom_plan_provider.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 {
+	CustomPlan		cplan;
+	List		   *ctid_quals;
+} CtidScanPlan;
+
+typedef struct {
+	CustomPlanState	cps;
+	List		   *ctid_quals;		/* list of ExprState for inequality ops */
+} CtidScanState;
+
+static CustomPathMethods	ctidscan_path_methods;
+static CustomPlanMethods	ctidscan_plan_methods;
+static CustomExecMethods	ctidscan_exec_methods;
+
+/* function declarations */
+void	_PG_init(void);
+Datum	CtidScanAddPath(PG_FUNCTION_ARGS);
+
+#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;
+}
+
+/*
+ * CreateCtidScanPlan - A method of CustomPath; that populate a custom
+ * object being delivered from CustomPlan type, according to the supplied
+ * CustomPath object.
+ */
+static Node *
+CreateCtidScanPlan(PlannerInfo *root, CustomPath *best_path)
+{
+	CtidScanPath   *ctid_path = (CtidScanPath *) best_path;
+	CtidScanPlan   *ctid_scan;
+
+	ctid_scan = palloc0(sizeof(CtidScanPlan));
+	NodeSetTag(ctid_scan, T_CustomPlan);
+	ctid_scan->cplan.methods = &ctidscan_plan_methods;
+	ctid_scan->ctid_quals = ctid_path->ctid_quals;
+
+	return (Node *)&ctid_scan->cplan;
+}
+
+/*
+ * InitCtidScanPlan
+ *
+ * main portion to initialize CustomPlan node.
+ */
+static Plan *
+InitCtidScanPlan(CustomPlan *custom_plan,
+				 PlannerInfo *root, CustomPath *best_path,
+				 List *tlist, List *clauses)
+{
+	CtidScanPlan   *ctid_scan = (CtidScanPlan *)custom_plan;
+	List		   *ctid_quals = ((CtidScanPath *)best_path)->ctid_quals;
+
+	Assert(ctid_scan->cplan.scan.scanrelid > 0);
+
+	/* Set targetlist as is */
+	ctid_scan->cplan.scan.plan.targetlist = tlist;
+	/* Reduce RestrictInfo list to bare expressions */
+	ctid_scan->cplan.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 &ctid_scan->cplan.scan.plan;
+}
+
+/*
+ * 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 CustomPlan; 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,
+				   CustomPlan *custom_plan,
+				   int rtoffset)
+{
+	CtidScanPlan   *ctidscan = (CtidScanPlan *) custom_plan;
+	Scan		   *scan = &ctidscan->cplan.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);
+}
+
+/*
+ * SupportCtidBackwardScan - A method of CustomPlan; that informs the core
+ * backend whether this custom-plan node support backward scan or not.
+ */
+static bool
+SupportCtidBackwardScan(CustomPlan *custom_plan)
+{
+	return true;
+}
+
+/*
+ * FinalizeCtidScanPlan - A method of CustomPlan; that handles callbacks
+ * by finalize_plan().
+ */
+static void
+FinalizeCtidScanPlan(PlannerInfo *root,
+					 CustomPlan *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 CustomPlan; that populate a custom
+ * object being delivered from CustomPlanState type, according to the
+ * supplied CustomPath object.
+ */
+static Node *
+CreateCtidScanState(CustomPlan *custom_plan)
+{
+	CtidScanState  *ctss = palloc0(sizeof(CtidScanState));
+
+	NodeSetTag(ctss, T_CustomPlanState);
+	ctss->cps.methods = &ctidscan_exec_methods;
+
+	return (Node *)&ctss->cps;
+}
+
+/*
+ * TextOutCtidScanPlan - A method of CustomPlan; that generates text
+ * representation of the given object.
+ */
+static void
+TextOutCtidScanPlan(StringInfo str, const CustomPlan *node)
+{
+	CtidScanPlan   *ctid_plan = (CtidScanPlan *) node;
+
+	appendStringInfo(str, " :ctid_quals %s",
+					 nodeToString(ctid_plan->ctid_quals));
+}
+
+/*
+ * CopyCtidScanPlan - A method of CustomPlan; that create a copied object.
+ */
+static CustomPlan *
+CopyCtidScanPlan(const CustomPlan *from)
+{
+	CtidScanPlan   *oldnode = (CtidScanPlan *) from;
+	CtidScanPlan   *newnode = palloc0(sizeof(CtidScanPlan));
+
+	NodeSetTag(newnode, T_CustomPlan);
+	newnode->cplan.methods = oldnode->cplan.methods;
+	newnode->ctid_quals = copyObject(oldnode->ctid_quals);
+
+	return &newnode->cplan;
+}
+
+/*
+ * BeginCtidScan - A method of CustomPlanState; that initializes
+ * the supplied CtidScanState object, at beginning of the executor.
+ */
+static void
+BeginCtidScan(CustomPlanState *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->cps.ss.ss_currentScanDesc = NULL;
+}
+
+/*
+ * ReScanCtidScan - A method of CustomPlanState; that rewind the current
+ * seek position.
+ */
+static void
+ReScanCtidScan(CustomPlanState *node)
+{
+	CtidScanState  *ctss = (CtidScanState *)node;
+	HeapScanDesc	scan = ctss->cps.ss.ss_currentScanDesc;
+	EState		   *estate = node->ss.ps.state;
+	ScanDirection	direction = estate->es_direction;
+	Relation		relation = ctss->cps.ss.ss_currentRelation;
+	ExprContext	   *econtext = ctss->cps.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->cps.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->cps.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->cps.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(CustomPlanState *node)
+{
+	CtidScanState  *ctss = (CtidScanState *) node;
+	HeapScanDesc	scan;
+	TupleTableSlot *slot;
+	EState		   *estate = node->ss.ps.state;
+	ScanDirection	direction = estate->es_direction;
+	HeapTuple		tuple;
+
+	if (!ctss->cps.ss.ss_currentScanDesc)
+		ReScanCtidScan(node);
+	scan = ctss->cps.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->cps.ss.ss_ScanTupleSlot;
+	ExecStoreTuple(tuple, slot, scan->rs_cbuf, false);
+
+	return slot;
+}
+
+static bool
+CTidRecheckCustomScan(CustomPlanState *node, TupleTableSlot *slot)
+{
+	return true;
+}
+
+/*
+ * ExecCtidScan - A method of CustomPlanState; that fetches a tuple
+ * from the relation, if exist anymore.
+ */
+static TupleTableSlot *
+ExecCtidScan(CustomPlanState *node)
+{
+	return ExecScan(&node->ss,
+					(ExecScanAccessMtd) CTidAccessCustomScan,
+					(ExecScanRecheckMtd) CTidRecheckCustomScan);
+}
+
+/*
+ * CTidEndCustomScan - A method of CustomPlanState; that closes heap and
+ * scan descriptor, and release other related resources.
+ */
+static void
+EndCtidScan(CustomPlanState *node)
+{
+	CtidScanState  *ctss = (CtidScanState *)node;
+
+	if (ctss->cps.ss.ss_currentScanDesc)
+		heap_endscan(ctss->cps.ss.ss_currentScanDesc);
+}
+
+/*
+ * ExplanCtidScanTargetRel - A method of CustomPlanState; that output
+ * relation's name to be scanned.
+ */
+static void
+ExplanCtidScanTargetRel(CustomPlanState *node, ExplainState *es)
+{
+	CtidScanState  *ctss = (CtidScanState *) node;
+	CtidScanPlan   *ctid_plan = (CtidScanPlan *) ctss->cps.ss.ps.plan;
+	Index			rti = ctid_plan->cplan.scan.scanrelid;
+	RangeTblEntry   *rte;
+	char		   *objectname = NULL;
+	char		   *namespace = NULL;
+	char		   *refname;
+
+	/* logic copied from ExplainTargetRel */
+	rte = rt_fetch(rti, es->rtable);
+	refname = (char *) list_nth(es->rtable_names, rti - 1);
+	if (refname == NULL)
+		refname = rte->eref->aliasname;
+
+	Assert(rte->rtekind == RTE_RELATION);
+	objectname = get_rel_name(rte->relid);
+	if (es->verbose)
+		namespace = get_namespace_name(get_rel_namespace(rte->relid));
+
+	if (es->format == EXPLAIN_FORMAT_TEXT)
+	{
+		appendStringInfoString(es->str, " on");
+		if (namespace != NULL)
+			appendStringInfo(es->str, " %s.%s", quote_identifier(namespace),
+							 quote_identifier(objectname));
+		else if (objectname != NULL)
+			appendStringInfo(es->str, " %s", quote_identifier(objectname));
+		if (objectname == NULL || strcmp(refname, objectname) != 0)
+			appendStringInfo(es->str, " %s", quote_identifier(refname));
+	}
+	else
+	{
+		if (objectname != NULL)
+			ExplainPropertyText("Relation Name", objectname, es);
+		if (namespace != NULL)
+			ExplainPropertyText("Schema", namespace, es);
+		ExplainPropertyText("Alias", refname, es);
+	}
+}
+
+/*
+ * ExplainCtidScan - A method of CustomPlanState; that shows extra info
+ * on EXPLAIN command.
+ */
+static void
+ExplainCtidScan(CustomPlanState *node, List *ancestors, ExplainState *es)
+{
+	CtidScanState  *ctss = (CtidScanState *) node;
+	CtidScanPlan   *ctid_plan = (CtidScanPlan *) ctss->cps.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);
+	}
+}
+
+/*
+ * ExplainCtidPreScanNode - A method of CustomPlanState; that informs
+ * the core backend relation's rtindex to be referenced, prior to the
+ * main EXPLAIN processing.
+ */
+static void
+ExplainCtidPreScanNode(CustomPlanState *node, Bitmapset **rels_used)
+{
+	CtidScanState  *ctss = (CtidScanState *) node;
+	Index			scanrelid = ((Scan *)ctss->cps.ss.ps.plan)->scanrelid;
+
+	*rels_used = bms_add_member(*rels_used, scanrelid);
+}
+
+/*
+ * Entrypoint of this extension
+ */
+Datum
+CtidScanAddPath(PG_FUNCTION_ARGS)
+{
+	customScanArg  *cscan_arg = (customScanArg *)PG_GETARG_POINTER(0);
+	PlannerInfo	   *root;
+	RangeTblEntry  *rte;
+	RelOptInfo	   *baserel;
+	char			relkind;
+	ListCell	   *lc;
+	List		   *ctid_quals = NIL;
+
+	if (cscan_arg->custom_class != CUSTOMPLAN_CLASS_SCAN)
+		PG_RETURN_VOID();
+
+	root = cscan_arg->root;
+	rte = cscan_arg->rte;
+	baserel = cscan_arg->baserel;
+
+	/* all we can support is regular relations */
+	if (rte->rtekind != RTE_RELATION)
+		PG_RETURN_VOID();
+
+	relkind = get_rel_relkind(rte->relid);
+	if (relkind != RELKIND_RELATION &&
+		relkind != RELKIND_MATVIEW &&
+		relkind != RELKIND_TOASTVALUE)
+		PG_RETURN_VOID();
+
+	/* 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_CustomPlan;
+		ctid_path->cpath.path.parent = baserel;
+		ctid_path->cpath.path.param_info
+			= get_baserel_parampathinfo(root, baserel, required_outer);
+		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);
+	}
+	PG_RETURN_VOID();
+}
+PG_FUNCTION_INFO_V1(CtidScanAddPath);
+
+/*
+ * Entrypoint of this extension
+ */
+void
+_PG_init(void)
+{
+	/* setup ctidscan_path_methods */
+	ctidscan_path_methods.CustomName = "ctidscan";
+	ctidscan_path_methods.CreateCustomPlan = CreateCtidScanPlan;
+	ctidscan_path_methods.TextOutCustomPath = TextOutCtidScanPath;
+
+	/* setup ctidscan_plan_methods */
+	ctidscan_plan_methods.CustomName = "ctidscan";
+	ctidscan_plan_methods.InitCustomPlan = InitCtidScanPlan;
+	ctidscan_plan_methods.SetCustomPlanRef = SetCtidScanPlanRef;
+	ctidscan_plan_methods.SupportBackwardScan = SupportCtidBackwardScan;
+	ctidscan_plan_methods.FinalizeCustomPlan = FinalizeCtidScanPlan;
+	ctidscan_plan_methods.CreateCustomPlanState = CreateCtidScanState;
+	ctidscan_plan_methods.TextOutCustomPlan = TextOutCtidScanPlan;
+	ctidscan_plan_methods.CopyCustomPlan = CopyCtidScanPlan;
+
+	/* setup ctidscan_planstate_methods */
+	ctidscan_exec_methods.CustomName = "ctidscan";
+	ctidscan_exec_methods.BeginCustomPlan = BeginCtidScan;
+	ctidscan_exec_methods.ExecCustomPlan = ExecCtidScan;
+	ctidscan_exec_methods.EndCustomPlan = EndCtidScan;
+	ctidscan_exec_methods.ReScanCustomPlan = ReScanCtidScan;
+	ctidscan_exec_methods.MarkPosCustomPlan = NULL;
+	ctidscan_exec_methods.RestrPosCustomPlan = NULL;
+	ctidscan_exec_methods.ExplainCustomPlanTargetRel = ExplanCtidScanTargetRel;
+	ctidscan_exec_methods.ExplainCustomPlan = ExplainCtidScan;
+	ctidscan_exec_methods.ExplainCustomPreScanNode = ExplainCtidPreScanNode;
+	ctidscan_exec_methods.GetSpecialCustomVar = NULL;
+}
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..74d66e6
--- /dev/null
+++ b/contrib/ctidscan/expected/ctidscan.out
@@ -0,0 +1,312 @@
+--
+-- Regression Tests for Custom Plan APIs
+--
+CREATE EXTENSION ctidscan;
+-- 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                              
+----------------------------------------------------------------------
+ Custom (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 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 (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 (ctidscan) on t1
+               Filter: (ctid < '(2,10)'::tid)
+               ctid quals: (ctid < '(2,10)'::tid)
+   ->  Sort
+         Sort Key: t2.ctid
+         ->  Custom (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 (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 (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)
+
+-- 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..213a97a
--- /dev/null
+++ b/contrib/ctidscan/sql/ctidscan.sql
@@ -0,0 +1,54 @@
+--
+-- Regression Tests for Custom Plan APIs
+--
+
+CREATE EXTENSION ctidscan;
+
+-- 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;
+
+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);
+
+-- Test cleanup
+DROP SCHEMA regtest_custom_scan CASCADE;
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index b4a06e4..3d7a289 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -109,6 +109,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-custom-plan-provider"><structname>pg_custom_plan_provider</structname></link></entry>
+      <entry>custom plan providers</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-conversion"><structname>pg_conversion</structname></link></entry>
       <entry>encoding conversion information</entry>
      </row>
@@ -2508,6 +2513,60 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-custom-plan-provider">
+  <title><structname>pg_custom_plan_provider</structname></title>
+
+  <indexterm zone="catalog-pg-custom-plan-provider">
+   <primary>pg_custom_plan_provider</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_custom_plan_provider</structname> describes
+   custom-plan providers. See <xref linkend="sql-createcustomplanprovider">
+   for more information.
+  </para>
+
+  <table>
+   <title><structname>pg_custom_plan_provider</> Columns</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>References</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><structfield>oid</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry></entry>
+      <entry>Row identifier (hidden attribute; must be explicitly selected)</entry>
+     </row>
+     <row>
+      <entry><structfield>cppname</structfield></entry>
+      <entry><type>name</type></entry>
+      <entry></entry>
+      <entry>custom-plan provider name</entry>
+     </row>
+     <row>
+      <entry><structfield>cppclass</structfield></entry>
+      <entry><type>char</type></entry>
+      <entry></entry>
+      <entry>class of custom-plan node on which this custom-plan provider can perform</entry>
+     </row>
+     <row>
+      <entry><structfield>cpphandler</structfield></entry>
+      <entry><type>regproc</type></entry>
+      <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
+      <entry>handler function of custom-plan provder that will propose an alternative query execution path</entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
  <sect1 id="catalog-pg-database">
   <title><structname>pg_database</structname></title>
 
diff --git a/doc/src/sgml/custom-plan-provider.sgml b/doc/src/sgml/custom-plan-provider.sgml
new file mode 100644
index 0000000..d86495b
--- /dev/null
+++ b/doc/src/sgml/custom-plan-provider.sgml
@@ -0,0 +1,431 @@
+<!-- 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 variable 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 the extension that provides custom-plan custom plan
+  provider.
+ </para>
+
+ <sect1 id="cpp-overall-steps">
+  <title>Custom Plan Overall Steps</title>
+  <para>
+   A custom plan provider can be registered using
+   <xref linkend="sql-createcustomplanprovider"> command.
+   It takes a class of custom plan and its handler function.
+   Class of custom plan specifies the task to be replaced by the custom-
+   plan being defined.
+   Only <literal>SCAN</> is the supported class of custom-plan right now.
+   Custom plan handler function is an entrypoint to the planner. It calls
+   the handler function during construction of query execution path.
+  </para>
+  <para>
+   Handler function has to be declared as a function that takes one
+   <literal>internal</> data type and returns <literal>void</>.
+   On invocation, query planner delivers a pointer of data structure
+   according to the custom plan class, custom-plan handler function will
+   decide whether it can offer alternative execution path towards the
+   required task. If available, it can add a <literal>CustomPath</>
+   (or inherited object type) as one of the candidate execution path
+   with estimated cost and set of callbacks defined in the
+   <literal>CustomPathMethods</> structure.
+  </para>
+  <para>
+   Planner compares all the potential execution paths based on its cost.
+   Once a custom path that was added by custom plan provider gets chosen,
+   <literal>CreateCustomPlan</> callback shall be called, to populate
+   a <literal>CustomPlan</> (or inherited object type) node according to
+   the <literal>CustomPath</> node preliminary constructed.
+   In the similar manner, <literal>CreateCustomPlanState</> callback shall
+   be called, to populate a <literal>CustomPlanState</> (or inherited
+   object type) node according to the <literal>CustomPlan</> node being
+   constructed above.
+   The reason why custom-plan provider has to allocate a node object by
+   itself is that we allow to extend the base types to store private
+   fields managed by individual custom-plan providers, thus only custom-
+   plan provider knows actual data size to be allocated.
+  </para>
+  <para>
+   Once a <literal>CustomPlanState</> is constructed, its callback
+   functions are invoked by executor, so custom-plan provider performs
+   required tasks then pops up the result.
+  </para>
+ </sect1>
+
+ <sect1 id="cpp-handler-function">
+  <title>Custom Plan Handler Functions</title>
+  <para>
+   A handler function that was specified on
+   the <xref linkend="sql-createcustomplanprovider"> command is
+   declared to return <literal>void</> data type and takes an
+   <literal>internal</> data type; that is usually applied to exchange
+   internal data structure.
+   This handler function is not an exception. It can reference a pointer
+   being informed via function argument to understand the context.
+  </para>
+  <para>
+   The data structure of arguments fully depend on the class of custom-
+   plan. In case of <literal>SCAN</> class (that is only supported one
+   right now), the first argument points the following data structure
+   that has enough information to construct a <literal>Path</> node.
+<programlisting>
+typedef struct {
+    uint32          cpp_class;
+    PlannerInfo    *root;
+    RelOptInfo     *baserel;
+    RangeTblEntry  *rte;
+} customScanArg;
+</programlisting>
+   <literal>cpp_class</>is always <literal>CUSTOMPLAN_CLASS_SCAN</>
+   that shows this structure is for scan class.
+   <literal>root</> is <literal>PlannerInfo</> of this plan,
+   <literal>baserel</> is <literal>RelOptInfo</> of the relation to
+   be scanned, and <literal>rte</> is <literal>RangeTblEntry</> of
+   the relation.
+  </para>
+  <para>
+   The custom-plan provider being invoked can check whether it can
+   provides alternative way to scan the relation. If available, it
+   shall construct <literal>CustomPath</> or its inherited one with
+   estimated cost and callbacks below, then register the path using
+   <literal>add_path</> towards the supplied <literal>RelOptInfo</>.
+  </para>
+ </sect1>
+
+ <sect1 id="cpp-path-callbacks">
+  <title>Custom Path Callbacks</title>
+  <para>
+   This section introduces callback functions of <literal>CustomPath</>
+   structure; defined in the <literal>CustomPathMethods</>.
+  </para>
+  <para>
+<programlisting>
+Node *
+CreateCustomPlan(PlannerInfo *root,
+                 CustomPath *best_path);
+</programlisting>
+   It populates a <literal>CustomPlan</> (or inherited data type) node
+   according to the supplied <literal>CustomPath</> node which was
+   constructed on the custom plan handler function then chosen by the
+   query planner.
+   Only custom plan provider can know exact size of the node to be
+   allocated, this callback allocate a <literal>CustomPlan</> node with
+   <literal>CustomPlanMethods</> callbacks table and arbitrary private
+   fields.
+  </para>
+  <para>
+   Note that the node tag can be assigned is either <literal>CustomPlan</>
+   or <literal>CustomPlanMarkPos</>. The later one indicates this plan
+   node supports mark and restore position during query execution, but
+   has identical data structure.
+  </para>
+
+
+  <para>
+<programlisting>
+void
+TextOutCustomPath(StringInfo str, const CustomPath *node);
+</programlisting>
+   It makes a text representation of custom path node. If custom-plan
+   provider extends <literal>CustomPath</> data type, it shall to put private
+   fields on the supplied <literal>StringInfo</> with text form.
+   Note that common fields in <literal>CustomPath</> are handled by backend,
+   so extension needs to do nothing special.
+  </para>
+ </sect1>
+
+ <sect1 id="cpp-plan-callbacks">
+  <title>Custom Plan Callbacks</title>
+  <para>
+   This section introduces callback functions of <literal>CustomPlan</>
+   structure; defined in the <literal>CustomPlanMethods</>.
+  </para>
+  <para>
+<programlisting>
+Plan      *
+InitCustomPlan(CustomPlan *custom_plan,
+               PlannerInfo *root,
+               CustomPath *best_path,
+               List *tlist,
+               List *clauses);
+</programlisting>
+   It initializes the <literal>CustomPlan</> node being acquired by
+   <literal>CreateCustomPlan</> callback.
+   The backend takes some common initializations prior to its invocation.
+   <literal>tlist</> and <literal>clauses</> are extracted from the path
+   node according to the usual manner, so all custom plan provider has to
+   do is putting these members if nothing special are done.
+  </para>
+
+  <para>
+<programlisting>
+void
+SetCustomPlanRef(PlannerInfo *root,
+                 CustomPlan *custom_plan,
+                 int rtoffset);
+</programlisting>
+   It adjusts <literal>varno</> and <literal>varattno</> of var-nodes in
+   the expression tree chained from <literal>CustomPlan</> 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 <literal>fix_scan_expr</> shall be called on expression nodes to
+   track plan dependency.
+  </para>
+
+  <para>
+<programlisting>
+bool
+SupportBackwardScan(CustomPlan *custom_plan);
+</programlisting>
+   It is an optional callback, which tells the backend whether this custom-
+   plan node supports backward scan or not. If <literal>NULL</>, it means
+   backward scan is not supported.
+  </para>
+
+  <para>
+<programlisting>
+void
+FinalizeCustomPlan(PlannerInfo *root,
+                   CustomPlan *custom_plan,
+                   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 custom-plan 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 custom-plan 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 *
+CreateCustomPlanState(CustomPlan *custom_plan);
+</programlisting>
+   It populates a <literal>CustomPlanState</> (or inherited data type)
+   node according to the supplied <literal>CustomPlan</> node preliminary
+   constructed, on the beginning of query executor.
+   Only custom plan provider can know exact size of the node to be
+   allocated, this callback allocate a <literal>CustomPlanState</> node
+   with <literal>CustomExecMethods</> callbacks table and arbitrary
+   private fields.
+  </para>
+  <para>
+   Note that main purpose of this callback is allocation of
+   <literal>CustomPlanState</> node, not initialization of individual
+   fields because it shall be handled on the <literal>BeginCustomPlan</>
+   callback to be invoked next to the common usual initialization.
+  </para>
+
+  <para>
+<programlisting>
+void
+TextOutCustomPlan(StringInfo str,
+                  const CustomPlan *node);
+</programlisting>
+   It makes a text representation of custom plan node. If custom-plan
+   provider extends <literal>CustomPlan</> data type, it shall put private
+   fields on the supplied <literal>StringInfo</> with text form.
+   Note that common fields in <literal>CustomPlan</> are handled by backend,
+   so extension needs to do nothing special.
+  </para>
+
+  <para>
+<programlisting>
+CustomPlan *
+CopyCustomPlan(const CustomPlan *from);
+</programlisting>
+   It duplicate a <literal>CustomPlan</> node onto a newly allocated one.
+   If custom-plan provider extends the <literal>CustomPlan</> node, it shall
+   copy the private fields and callback table but no need to copy the common
+   <literal>scan</> field in the <literal>CustomPlan</> data type.
+  </para>
+ </sect1>
+
+ <sect1 id="cpp-exec-callbacks">
+  <title>Custom Executor Callbacks</title>
+  <para>
+   This section introduces callback functions of <literal>CustomPlanState</>
+   structure; defined in the <literal>CustomExecMethods</>.
+  </para>
+  <para>
+<programlisting>
+void
+BeginCustomPlan(CustomPlanState *node,
+                EState *estate,
+                int eflags);
+</programlisting>
+   It begins execution of custom-plan. This callback is invoked during
+   executor startup to initialize the supplied <literal>CustomPlanState</>
+   that was constructed on the <literal>CreateCustomPlanState</> above.
+   The custom-plan provider shall have initialization of its private fields
+   and common fields within <literal>CustomPlanState</> if needed, because
+   backend code already initializes expressions on its <literal>targetlist</>
+   and <literal>qual</>, assigns result slot according to the
+   <literal>targetlist</> and also assigns scan slot if <literal>scanrelid</>
+   is valid.
+  </para>
+
+  <para>
+<programlisting>
+TupleTableSlot *
+ExecCustomPlan(CustomPlanState *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
+   <literal>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 <literal>EState</>, to acquire per-scan
+   duration memory.
+  </para>
+
+  <para>
+<programlisting>
+Node *
+MultiExecCustomPlan(CustomPlanState *node);
+</programlisting>
+   It allows to return arbitrary data structure to the upper node, unlike
+   usual <literal>ExecCustomPlan</>. Built-in code has no invocation path
+   to call <literal>MultiExecProcNode</> towards <literal>CustomPlanState</>
+   node, so it is invoked only when a particular custom-plan provider made
+   a stacked custom-plan nodes and called <literal>MultiExecProcNode</> to
+   this underlying node.
+  </para>
+  <para>
+   Note that it is custom-plan provider's responsibility to translate the
+   arbitrary data structure into <productname>PostgreSQL</>'s complianced
+   data structure when top-level <literal>CustomPlanState</> returns a row
+   using <literal>ExecCustomPlan</>.
+  </para>
+
+  <para>
+<programlisting>
+void
+EndCustomPlan(CustomPlanState *node);
+</programlisting>
+   It ends the execution of custom plan and release any resources held by
+   this node. If custom-plan provider acquired resources that is not
+   released automatically at end of executor, it is responsibility of the
+   custom plan provider.
+  </para>
+
+  <para>
+<programlisting>
+void
+ReScanCustomPlan(CustomPlanState *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 *
+MarkPosCustomPlan(CustomPlanState *node);
+</programlisting>
+   It is an optional callback, if <literal>CustomPlanState</> was populated
+   from the <literal>CustomPlanMarkPos</>. Elsewhere, it shall not be called
+   and <literal>NULL</> is suitable.
+   It saves current scan position on somewhere in private fields of
+   <literal>CustomPlanState</>, to restore the position later.
+  </para>
+
+  <para>
+<programlisting>
+void *
+RestrPosCustomPlan(CustomPlanState *node);
+</programlisting>
+   It is an optional callback, if <literal>CustomPlanState</> was populated
+   from the <literal>CustomPlanMarkPos</>. Elsewhere, it shall not be called
+   and <literal>NULL</> is suitable.
+   It restores the previous scan position saved by
+   the <literal>MarkPosCustomPlan</> above.
+  </para>
+
+  <para>
+<programlisting>
+void
+ExplainCustomPlanTargetRel(CustomPlanState *node,
+                           ExplainState *es);
+</programlisting>
+   It is an optional callback, to show the target relation to be scanned.
+   In most cases, custom plan provider put text representation of the relation
+   to be scanned according to the manner in <literal>ExplainTargetRel</>.
+  </para>
+
+  <para>
+<programlisting>
+void
+ExplainCustomPlan(CustomPlanState *node,
+                  List *ancestors,
+                  ExplainState *es);
+</programlisting>
+   It is an optional callback, to show custom-plan specific explain output.
+  </para>
+
+  <para>
+<programlisting>
+void
+ExplainCustomPreScanNode(CustomPlanState *node,
+                         Bitmapset **rels_used);
+</programlisting>
+   It is an optional callback, to inform the backend which relation is
+   referenced. It shall set <literal>scanrelid</> of the target relation.
+   If <literal>NULL</>, it means this custom-plan provider never
+   references base relations.
+  </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>CustomPlanState</> when <literal>EXPLAIN</> needs the
+   text form of the column actually referenced.
+   In case when custom-plan provider adjusted <literal>varno</> of varnodes
+   on the expression tree to use special varnos (<literal>INNER_VAR</>,
+   <literal>OUTER_VAR</> or <literal>INDEX_VAR</>), custom-plan 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 <literal>PlanState</> that shall be
+   set on the <literal>child_ps</> argument.
+  </para>
+ </sect1>
+</chapter>
+
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 5902f97..a9b9efc 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-provider SYSTEM "custom-plan-provider.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..4702178 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -242,6 +242,7 @@
   &nls;
   &plhandler;
   &fdwhandler;
+  &custom-plan-provider;
   &geqo;
   &indexam;
   &gist;
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index b685e16..ab35742 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -55,6 +55,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY createCast         SYSTEM "create_cast.sgml">
 <!ENTITY createCollation    SYSTEM "create_collation.sgml">
 <!ENTITY createConversion   SYSTEM "create_conversion.sgml">
+<!ENTITY createCustomPlanProvider SYSTEM "create_custom_plan_provider.sgml">
 <!ENTITY createDatabase     SYSTEM "create_database.sgml">
 <!ENTITY createDomain       SYSTEM "create_domain.sgml">
 <!ENTITY createEventTrigger SYSTEM "create_event_trigger.sgml">
@@ -95,6 +96,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY dropCast           SYSTEM "drop_cast.sgml">
 <!ENTITY dropCollation      SYSTEM "drop_collation.sgml">
 <!ENTITY dropConversion     SYSTEM "drop_conversion.sgml">
+<!ENTITY dropCustomPlanProvider SYSTEM "drop_custom_plan_provider.sgml">
 <!ENTITY dropDatabase       SYSTEM "drop_database.sgml">
 <!ENTITY dropDomain         SYSTEM "drop_domain.sgml">
 <!ENTITY dropEventTrigger   SYSTEM "drop_event_trigger.sgml">
diff --git a/doc/src/sgml/ref/create_custom_plan_provider.sgml b/doc/src/sgml/ref/create_custom_plan_provider.sgml
new file mode 100644
index 0000000..0816584
--- /dev/null
+++ b/doc/src/sgml/ref/create_custom_plan_provider.sgml
@@ -0,0 +1,139 @@
+<!--
+doc/src/sgml/ref/create_custom_plan_provider.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-CREATECUSTOMPLANPROVIDER">
+ <indexterm zone="sql-createcustomplanprovider">
+  <primary>CREATE CUSTOM PLAN PROVIDER</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>CREATE CUSTOM PLAN PROVIDER</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>CREATE CUSTOM PLAN PROVIDER</refname>
+  <refpurpose>define a new custom plan provider</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+CREATE CUSTOM PLAN PROVIDER <replaceable class="parameter">cpp_name</replaceable> FOR <replaceable class="parameter">cpp_class</replaceable>
+    HANDLER <replaceable class="parameter">handler_function</replaceable>
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>CREATE CUSTOM PLAN PROVIDER</command> defines a new custom-plan
+   provider.
+   The user who defines the custom-plan provider has to be a superuser.
+  </para>
+
+  <para>
+   A custom-plan provider can offer the query planner alternative options
+   to scan relation, or potentially join relations and so on, in addition
+   to the built-in logics. It is usually extension modules that implement
+   callbacks according to the custom-plan interface.
+  </para>
+  <para>
+   This statement defines a couple of an entrypoint of custom-plan provider
+   and its supporting workload type. Right now, <literal>scan</literal> is
+   the only class being supported; that enables to call extension's
+   callback during query execution instead of built-in routines like
+   <literal>SeqScan</literal> or <literal>IndexScan</literal> if its
+   cost estimation is enough reasonable.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">cpp_name</replaceable></term>
+    <listitem>
+     <para>
+      The name of the custom-plan provider to be created.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">cpp_class</replaceable></term>
+    <listitem>
+     <para>
+      Workload type on which custom-plan provider can perform.
+      Only <literal>SCAN</literal> is supported option right now.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">handler_function</replaceable></term>
+    <listitem>
+     <para>
+      A function to be called when query planner is finding the best path
+      to scan a relation.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+   The function that performs as a custom-plan provider shall be declared
+   to return <literal>void</> and take one argument with <literal>internal</>
+   data type.
+   The core <productname>PostgreSQL</> calls custom-plan provider function
+   with a set of information about planner's state and relation(s) to be
+   scanned, then this function shall check whether it can offer alternative
+   scan paths or not.
+   If available, it constructs a path object derived from
+   <literal>CustomPath</> structure, that contains a set of callbacks
+   including the ones to populate <literal>CustomPlan</> or
+   <literal>CustomPlanState</> object later.
+   If extension needs to save its private information in these object,
+   define a new structure that extends above data types.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+  <para>
+   Create a custom-plan provider <literal>ctidscan</> that uses the funcion
+   <literal>ctidscanaddpath</>.   
+<programlisting>
+CREATE CUSTOM PLAN PROVIDER ctidscan FOR scan HANDLER ctidscanaddpath;
+</programlisting>
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+  <para>
+   There is no <command>CREATE CUSTOM PLAN</command> command
+   in the SQL standard.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-dropcustomplanprovider"></member>
+  </simplelist>
+  <simplelist type="inline">
+   <member><xref linkend="custom-plan"></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/drop_custom_plan_provider.sgml b/doc/src/sgml/ref/drop_custom_plan_provider.sgml
new file mode 100644
index 0000000..6a305a8
--- /dev/null
+++ b/doc/src/sgml/ref/drop_custom_plan_provider.sgml
@@ -0,0 +1,108 @@
+<!--
+doc/src/sgml/ref/drop_custom_plan_provider.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-DROPCUSTOMPLANPROVIDER">
+ <indexterm zone="sql-dropcustomplanprovider">
+  <primary>DROP CUSTOM PLAN PROVIDER</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>DROP CUSTOM PLAN PROVIDER</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>DROP CUSTOM PLAN PROVIDER</refname>
+  <refpurpose>remove a custom-plan provider</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+DROP CUSTOM PLAN PROVIDER [ IF EXISTS ] <replaceable class="parameter">cpp_name</replaceable> [ CASCADE | RESTRICT ]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>DROP CUSTOM PLAN PROVIDER</command> removes an existing custom
+   plan provider. To execute this command, the current user must be superuser.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>IF EXISTS</literal></term>
+    <listitem>
+     <para>
+      Do not throw an error if the custom-plan provider does not exist.
+      A notice is issued in this case.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name of an existing custom-plan provider.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>CASCADE</literal></term>
+    <listitem>
+     <para>
+      Automatically drop objects that depend on the custom-plan provider.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>RESTRICT</literal></term>
+    <listitem>
+     <para>
+      Refuse to drop the custom-plan provider if any objects depend on it.
+      This is the default.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   Drop a custom-plan provider <literal>foo</> if it exists:
+<programlisting>
+DROP CUSTOM PLAN PROVIDER IF EXISTS foo;
+</programlisting></para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   There is no <command>DROP CUSTOM PLAN</command> command
+   in the SQL standard.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-createcustomplanprovider"></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index 6ec1263..1a3dbdd 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -83,6 +83,7 @@
    &createCast;
    &createCollation;
    &createConversion;
+   &createCustomPlanProvider;
    &createDatabase;
    &createDomain;
    &createEventTrigger;
@@ -123,6 +124,7 @@
    &dropCast;
    &dropCollation;
    &dropConversion;
+   &dropCustomPlanProvider;
    &dropDatabase;
    &dropDomain;
    &dropEventTrigger;
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index a974bd5..f7e29eb 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -39,7 +39,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
 	pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
 	pg_ts_parser.h pg_ts_template.h pg_extension.h \
 	pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
-	pg_foreign_table.h \
+	pg_foreign_table.h pg_custom_plan_provider.h \
 	pg_default_acl.h pg_seclabel.h pg_shseclabel.h pg_collation.h pg_range.h \
 	toasting.h indexing.h \
     )
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index d41ba49..496bc9a 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -30,6 +30,7 @@
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_conversion.h"
 #include "catalog/pg_conversion_fn.h"
+#include "catalog/pg_custom_plan_provider.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_default_acl.h"
 #include "catalog/pg_depend.h"
@@ -154,7 +155,8 @@ static const Oid object_classes[MAX_OCLASS] = {
 	UserMappingRelationId,		/* OCLASS_USER_MAPPING */
 	DefaultAclRelationId,		/* OCLASS_DEFACL */
 	ExtensionRelationId,		/* OCLASS_EXTENSION */
-	EventTriggerRelationId		/* OCLASS_EVENT_TRIGGER */
+	EventTriggerRelationId,		/* OCLASS_EVENT_TRIGGER */
+	CustomPlanProviderRelationId,		/* OCLASS_CPP */
 };
 
 
@@ -1249,6 +1251,10 @@ doDeletion(const ObjectAddress *object, int flags)
 			RemoveEventTriggerById(object->objectId);
 			break;
 
+		case OCLASS_CPP:
+			RemoveCustomPlanProviderById(object->objectId);
+			break;
+
 		default:
 			elog(ERROR, "unrecognized object class: %u",
 				 object->classId);
@@ -2316,6 +2322,9 @@ getObjectClass(const ObjectAddress *object)
 
 		case EventTriggerRelationId:
 			return OCLASS_EVENT_TRIGGER;
+
+		case CustomPlanProviderRelationId:
+			return OCLASS_CPP;
 	}
 
 	/* shouldn't get here */
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index c7c8f4b..2b0b0e1 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -30,6 +30,7 @@
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_conversion.h"
+#include "catalog/pg_custom_plan_provider.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_extension.h"
 #include "catalog/pg_foreign_data_wrapper.h"
@@ -152,6 +153,18 @@ static const ObjectPropertyType ObjectProperty[] =
 		true
 	},
 	{
+		CustomPlanProviderRelationId,
+		CustomPlanProviderOidIndexId,
+		CUSTOMPLANPROVIDEROID,
+		CUSTOMPLANPROVIDERNAME,
+		Anum_pg_custom_plan_provider_cppname,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		-1,
+		false,
+	},
+	{
 		DatabaseRelationId,
 		DatabaseOidIndexId,
 		DATABASEOID,
@@ -529,6 +542,7 @@ get_object_address(ObjectType objtype, List *objname, List *objargs,
 			case OBJECT_FDW:
 			case OBJECT_FOREIGN_SERVER:
 			case OBJECT_EVENT_TRIGGER:
+			case OBJECT_CPP:
 				address = get_object_address_unqualified(objtype,
 														 objname, missing_ok);
 				break;
@@ -755,6 +769,9 @@ get_object_address_unqualified(ObjectType objtype,
 			case OBJECT_EVENT_TRIGGER:
 				msg = gettext_noop("event trigger name cannot be qualified");
 				break;
+			case OBJECT_CPP:
+				msg = gettext_noop("custom plan provider name cannot be qualified");
+				break;
 			default:
 				elog(ERROR, "unrecognized objtype: %d", (int) objtype);
 				msg = NULL;		/* placate compiler */
@@ -815,6 +832,11 @@ get_object_address_unqualified(ObjectType objtype,
 			address.objectId = get_event_trigger_oid(name, missing_ok);
 			address.objectSubId = 0;
 			break;
+		case OBJECT_CPP:
+			address.classId = CustomPlanProviderRelationId;
+			address.objectId = get_custom_plan_provider_oid(name, missing_ok);
+			address.objectSubId = 0;
+			break;
 		default:
 			elog(ERROR, "unrecognized objtype: %d", (int) objtype);
 			/* placate compiler, which doesn't know elog won't return */
@@ -1295,6 +1317,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
 			break;
 		case OBJECT_TSPARSER:
 		case OBJECT_TSTEMPLATE:
+		case OBJECT_CPP:
 			/* We treat these object types as being owned by superusers */
 			if (!superuser_arg(roleid))
 				ereport(ERROR,
@@ -2166,6 +2189,24 @@ getObjectDescription(const ObjectAddress *object)
 				break;
 			}
 
+		case OCLASS_CPP:
+			{
+				Form_pg_custom_plan_provider cpp_form;
+				HeapTuple	tup;
+
+				tup = SearchSysCache1(CUSTOMPLANPROVIDEROID,
+									  ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR,
+						 "cache lookup failed for custom-plan provider %u",
+						 object->objectId);
+				cpp_form = (Form_pg_custom_plan_provider) GETSTRUCT(tup);
+				appendStringInfo(&buffer, _("custom plan provider %s"),
+								 NameStr(cpp_form->cppname));
+				ReleaseSysCache(tup);
+				break;
+			}
+
 		default:
 			appendStringInfo(&buffer, "unrecognized object %u %u %d",
 							 object->classId,
@@ -2577,6 +2618,10 @@ getObjectTypeDescription(const ObjectAddress *object)
 			appendStringInfoString(&buffer, "event trigger");
 			break;
 
+		case OCLASS_CPP:
+			appendStringInfoString(&buffer, "custom plan provider");
+			break;
+
 		default:
 			appendStringInfo(&buffer, "unrecognized %u", object->classId);
 			break;
@@ -3330,6 +3375,24 @@ getObjectIdentity(const ObjectAddress *object)
 				break;
 			}
 
+		case OCLASS_CPP:
+			{
+				HeapTuple	tup;
+				Form_pg_custom_plan_provider cpp_form;
+
+				tup = SearchSysCache1(CUSTOMPLANPROVIDEROID,
+									  ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR,
+						 "cache lookup failed for custom-plan provider %u",
+						 object->objectId);
+				cpp_form = (Form_pg_custom_plan_provider) GETSTRUCT(tup);
+				appendStringInfoString(&buffer,
+						 quote_identifier(NameStr(cpp_form->cppname)));
+				ReleaseSysCache(tup);
+				break;
+			}
+
 		default:
 			appendStringInfo(&buffer, "unrecognized object %u %u %d",
 							 object->classId,
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index 22f116b..1e8e6f4 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -14,7 +14,7 @@ include $(top_builddir)/src/Makefile.global
 
 OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o  \
 	collationcmds.o constraint.o conversioncmds.o copy.o createas.o \
-	dbcommands.o define.o discard.o dropcmds.o \
+	custom_plan.o dbcommands.o define.o discard.o dropcmds.o \
 	event_trigger.o explain.o extension.o foreigncmds.o functioncmds.o \
 	indexcmds.o lockcmds.o matview.o operatorcmds.o opclasscmds.o \
 	portalcmds.o prepare.o proclang.o \
diff --git a/src/backend/commands/custom_plan.c b/src/backend/commands/custom_plan.c
new file mode 100644
index 0000000..2a81a7b
--- /dev/null
+++ b/src/backend/commands/custom_plan.c
@@ -0,0 +1,188 @@
+/*-------------------------------------------------------------------------
+ *
+ * custom_plan.c
+ *		custom plan nodes creation/manipulation commands
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ *    src/backend/commands/custom_plan.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_custom_plan_provider.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+#include "commands/defrem.h"
+#include "fmgr.h"
+#include "miscadmin.h"
+#include "parser/parse_func.h"
+#include "utils/builtins.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+/*
+ * utility function to lookup a custom-plan provider by name
+ */
+Oid
+get_custom_plan_provider_oid(const char *cpp_name, bool missing_ok)
+{
+	Oid		cpp_oid;
+
+	cpp_oid = GetSysCacheOid1(CUSTOMPLANPROVIDERNAME,
+							   CStringGetDatum(cpp_name));
+	if (!OidIsValid(cpp_oid) && !missing_ok)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("custom-plan provider \"%s\" does not exist",
+						cpp_name)));
+	return cpp_oid;
+}
+
+/*
+ * Drop a custom-plan provider
+ */
+void
+RemoveCustomPlanProviderById(Oid cpp_oid)
+{
+	Relation	rel;
+	HeapTuple	tuple;
+
+	rel = heap_open(CustomPlanProviderRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCache1(CUSTOMPLANPROVIDEROID,
+							ObjectIdGetDatum(cpp_oid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for custom-plan provider %u",
+			 cpp_oid);
+
+	simple_heap_delete(rel, &tuple->t_self);
+
+	ReleaseSysCache(tuple);
+
+	heap_close(rel, RowExclusiveLock);
+}
+
+/*
+ * Create a custom-plan provider
+ */
+Oid
+DefineCustomPlanProvider(CreateCustomPlanProviderStmt *stmt)
+{
+	Relation	rel;
+	Oid			cpp_oid;
+	Oid			cpp_handler = InvalidOid;
+	Datum		values[Natts_pg_custom_plan_provider];
+	bool		isnull[Natts_pg_custom_plan_provider];
+	HeapTuple	tuple;
+	ListCell   *cell;
+	ObjectAddress myself;
+	ObjectAddress referenced;
+
+	rel = heap_open(CustomPlanProviderRelationId, RowExclusiveLock);
+
+	/* must be super user */
+	if (!superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+			 errmsg("permission denied to create custom-plan provider \"%s\"",
+					stmt->cpp_name),
+			 errhint("Must be superuser to create a custom-plan node.")));
+
+	/* check namespace conflicts */
+	cpp_oid = get_custom_plan_provider_oid(stmt->cpp_name, true);
+	if (OidIsValid(cpp_oid))
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("custom-plan provider \"%s\" already exists",
+						stmt->cpp_name)));
+
+	/* check custom-plan class */
+	if (stmt->cpp_class != CUSTOMPLAN_CLASS_SCAN)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("unexpected custom plan class specified: %d",
+						(int)stmt->cpp_class)));
+
+	/* parse custom-plan options */
+	foreach (cell, stmt->cpp_options)
+	{
+		DefElem	   *defel = lfirst(cell);
+
+		Assert(IsA(defel, DefElem));
+
+		if (strcmp(defel->defname, "handler") == 0)
+		{
+			Oid		argtypes[1];
+
+			if (OidIsValid(cpp_handler))
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+
+			argtypes[0] = INTERNALOID;
+			cpp_handler = LookupFuncName((List *)defel->arg,
+										 1, argtypes, false);
+			if (get_func_rettype(cpp_handler) != VOIDOID)
+				ereport(ERROR,
+						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+						 errmsg("function %s must return type \"void\"",
+								NameListToString((List *) defel->arg))));
+		}
+		else
+			elog(ERROR, "unexpected custom-plan provider option: %s",
+				 defel->defname);
+	}
+
+	if (!OidIsValid(cpp_handler))
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("HANDLER must be provided")));
+
+	/*
+	 * Insert tuple into pg_custom_plan system catalog
+	 */
+	memset(values, 0, sizeof(values));
+	memset(isnull, 0, sizeof(isnull));
+	values[Anum_pg_custom_plan_provider_cppname - 1]
+		= DirectFunctionCall1(namein, CStringGetDatum(stmt->cpp_name));
+	values[Anum_pg_custom_plan_provider_cppclass - 1]
+		= stmt->cpp_class;
+	values[Anum_pg_custom_plan_provider_cpphandler - 1]
+		= ObjectIdGetDatum(cpp_handler);
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), values, isnull);
+
+	cpp_oid = simple_heap_insert(rel, tuple);
+	CatalogUpdateIndexes(rel, tuple);
+
+	heap_freetuple(tuple);
+
+	/* record dependencies */
+	myself.classId = CustomPlanProviderRelationId;
+	myself.objectId = cpp_oid;
+	myself.objectSubId = 0;
+
+	referenced.classId = ProcedureRelationId;
+	referenced.objectId = cpp_handler;
+	referenced.objectSubId = 0;
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+	/* Post creation hook for new custom-plan provider */
+	InvokeObjectPostCreateHook(CustomPlanProviderRelationId, cpp_oid, 0);
+
+	heap_close(rel, RowExclusiveLock);
+
+	return cpp_oid;
+}
diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c
index e64ad80..c6d4576 100644
--- a/src/backend/commands/dropcmds.c
+++ b/src/backend/commands/dropcmds.c
@@ -408,6 +408,11 @@ does_not_exist_skipping(ObjectType objtype, List *objname, List *objargs)
 				args = strVal(linitial(objargs));
 			}
 			break;
+		case OBJECT_CPP:
+			msg = gettext_noop("custom-plan provider \"%s\" does not exist, skipping");
+			name = NameListToString(objname);
+			break;
+
 		default:
 			elog(ERROR, "unexpected object type (%d)", (int) objtype);
 			break;
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 754264e..76968a2 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -923,6 +923,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
 		case OBJECT_CONSTRAINT:
 		case OBJECT_COLLATION:
 		case OBJECT_CONVERSION:
+		case OBJECT_CPP:
 		case OBJECT_DOMAIN:
 		case OBJECT_EXTENSION:
 		case OBJECT_FDW:
@@ -975,6 +976,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
 		case OCLASS_COLLATION:
 		case OCLASS_CONSTRAINT:
 		case OCLASS_CONVERSION:
+		case OCLASS_CPP:
 		case OCLASS_DEFAULT:
 		case OCLASS_LANGUAGE:
 		case OCLASS_LARGEOBJECT:
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 781a736..7ae2160 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -722,6 +722,15 @@ ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
 			*rels_used = bms_add_member(*rels_used,
 										((Scan *) plan)->scanrelid);
 			break;
+		case T_CustomPlan:
+		case T_CustomPlanMarkPos:
+			{
+				CustomPlanState *cps = (CustomPlanState *)planstate;
+
+				if (cps->methods->ExplainCustomPreScanNode)
+					cps->methods->ExplainCustomPreScanNode(cps, rels_used);
+			}
+			break;
 		case T_ModifyTable:
 			/* cf ExplainModifyTarget */
 			*rels_used = bms_add_member(*rels_used,
@@ -848,6 +857,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 +946,14 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_ForeignScan:
 			pname = sname = "Foreign Scan";
 			break;
+		case T_CustomPlan:
+			sname = "Custom";
+			custom_name = ((CustomPlan *) plan)->methods->CustomName;
+			if (custom_name)
+				pname = psprintf("Custom (%s)", custom_name);
+			else
+				pname = sname;
+			break;
 		case T_Material:
 			pname = sname = "Materialize";
 			break;
@@ -1037,6 +1055,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", custom_name, es);
 	}
 
 	switch (nodeTag(plan))
@@ -1084,6 +1104,14 @@ ExplainNode(PlanState *planstate, List *ancestors,
 					ExplainPropertyText("Index Name", indexname, es);
 			}
 			break;
+		case T_CustomPlan:
+			{
+				CustomPlanState *cps = (CustomPlanState *)planstate;
+
+				if (cps->methods->ExplainCustomPlanTargetRel)
+					cps->methods->ExplainCustomPlanTargetRel(cps, es);
+			}
+			break;
 		case T_ModifyTable:
 			ExplainModifyTarget((ModifyTable *) plan, es);
 			break;
@@ -1353,6 +1381,18 @@ ExplainNode(PlanState *planstate, List *ancestors,
 										   planstate, es);
 			show_foreignscan_info((ForeignScanState *) planstate, es);
 			break;
+		case T_CustomPlan:
+			{
+				CustomPlanState *cps = (CustomPlanState *) planstate;
+
+				show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
+				if (plan->qual)
+					show_instrumentation_count("Rows Removed by Filter", 1,
+											   planstate, es);
+				if (cps->methods->ExplainCustomPlan)
+					cps->methods->ExplainCustomPlan(cps, 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..800c969 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -21,6 +21,7 @@
 #include "executor/nodeBitmapIndexscan.h"
 #include "executor/nodeBitmapOr.h"
 #include "executor/nodeCtescan.h"
+#include "executor/nodeCustom.h"
 #include "executor/nodeForeignscan.h"
 #include "executor/nodeFunctionscan.h"
 #include "executor/nodeGroup.h"
@@ -197,6 +198,10 @@ ExecReScan(PlanState *node)
 			ExecReScanForeignScan((ForeignScanState *) node);
 			break;
 
+		case T_CustomPlanState:
+			ExecReScanCustomPlan((CustomPlanState *) node);
+			break;
+
 		case T_NestLoopState:
 			ExecReScanNestLoop((NestLoopState *) node);
 			break;
@@ -291,6 +296,10 @@ ExecMarkPos(PlanState *node)
 			ExecValuesMarkPos((ValuesScanState *) node);
 			break;
 
+		case T_CustomPlanState:
+			ExecCustomMarkPos((CustomPlanState *) node);
+			break;
+
 		case T_MaterialState:
 			ExecMaterialMarkPos((MaterialState *) node);
 			break;
@@ -348,6 +357,10 @@ ExecRestrPos(PlanState *node)
 			ExecValuesRestrPos((ValuesScanState *) node);
 			break;
 
+		case T_CustomPlanState:
+			ExecCustomRestrPos((CustomPlanState *) node);
+			break;
+
 		case T_MaterialState:
 			ExecMaterialRestrPos((MaterialState *) node);
 			break;
@@ -390,6 +403,7 @@ ExecSupportsMarkRestore(NodeTag plantype)
 		case T_ValuesScan:
 		case T_Material:
 		case T_Sort:
+		case T_CustomPlanMarkPos:
 			return true;
 
 		case T_Result:
@@ -465,6 +479,17 @@ ExecSupportsBackwardScan(Plan *node)
 			return ExecSupportsBackwardScan(((SubqueryScan *) node)->subplan) &&
 				TargetListSupportsBackwardScan(node->targetlist);
 
+		case T_CustomPlan:
+			{
+				CustomPlan *cplan = (CustomPlan *) node;
+
+				if (!TargetListSupportsBackwardScan(node->targetlist))
+					return false;
+				if (cplan->methods->SupportBackwardScan)
+					return cplan->methods->SupportBackwardScan(cplan);
+			}
+			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..62ebab9 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,12 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
 													   estate, eflags);
 			break;
 
+		case T_CustomPlan:
+		case T_CustomPlanMarkPos:
+			result = (PlanState *) ExecInitCustomPlan((CustomPlan *) node,
+													  estate, eflags);
+			break;
+
 			/*
 			 * join nodes
 			 */
@@ -442,6 +449,10 @@ ExecProcNode(PlanState *node)
 			result = ExecForeignScan((ForeignScanState *) node);
 			break;
 
+		case T_CustomPlanState:
+			result = ExecCustomPlan((CustomPlanState *) node);
+			break;
+
 			/*
 			 * join nodes
 			 */
@@ -558,6 +569,10 @@ MultiExecProcNode(PlanState *node)
 			result = MultiExecBitmapOr((BitmapOrState *) node);
 			break;
 
+		case T_CustomPlanState:
+			result = MultiExecCustomPlan((CustomPlanState *) node);
+			break;
+
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
 			result = NULL;
@@ -678,6 +693,10 @@ ExecEndNode(PlanState *node)
 			ExecEndForeignScan((ForeignScanState *) node);
 			break;
 
+		case T_CustomPlanState:
+			ExecEndCustomPlan((CustomPlanState *) node);
+			break;
+
 			/*
 			 * join nodes
 			 */
diff --git a/src/backend/executor/nodeCustom.c b/src/backend/executor/nodeCustom.c
new file mode 100644
index 0000000..785909e
--- /dev/null
+++ b/src/backend/executor/nodeCustom.c
@@ -0,0 +1,147 @@
+/* ------------------------------------------------------------------------
+ *
+ * nodeCustom.c
+ *		Routines to handle execution of custom plan 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"
+
+CustomPlanState *
+ExecInitCustomPlan(CustomPlan *cplan, EState *estate, int eflags)
+{
+	CustomPlanState    *cps;
+
+	/* populate a CustomPlanState according to the CustomPlan */
+	cps = (CustomPlanState *)cplan->methods->CreateCustomPlanState(cplan);
+	Assert(IsA(cps, CustomPlanState));
+
+	/* fill up fields of PlanState */
+	cps->ss.ps.plan = &cplan->scan.plan;
+	cps->ss.ps.state = estate;
+
+	/* create expression context for node */
+	ExecAssignExprContext(estate, &cps->ss.ps);
+	cps->ss.ps.ps_TupFromTlist = false;
+
+	/* initialize child expressions */
+	cps->ss.ps.targetlist = (List *)
+		ExecInitExpr((Expr *) cplan->scan.plan.targetlist,
+					 (PlanState *) cps);
+	cps->ss.ps.qual = (List *)
+		ExecInitExpr((Expr *) cplan->scan.plan.qual,
+					 (PlanState *) cps);
+
+	/* initialization of result tuple slot  */
+	ExecInitResultTupleSlot(estate, &cps->ss.ps);
+	ExecAssignResultTypeFromTL(&cps->ss.ps);
+
+	if (cplan->scan.scanrelid > 0)
+	{
+		Relation	heap_rel;
+
+		heap_rel = ExecOpenScanRelation(estate, cplan->scan.scanrelid, eflags);
+		cps->ss.ss_currentRelation = heap_rel;
+		cps->ss.ss_currentScanDesc = NULL;	/* set by provider on demand */
+		ExecInitScanTupleSlot(estate, &cps->ss);
+		ExecAssignScanType(&cps->ss, RelationGetDescr(heap_rel));
+		ExecAssignScanProjectionInfo(&cps->ss);
+	}
+	else
+	{
+		/*
+		 * Elsewhere, custom-plan provider should be responsible to put
+		 * appropriate initialization of scan tuple-slot and projection
+		 * info.
+		 */
+		cps->ss.ss_currentRelation = NULL;
+		cps->ss.ss_currentScanDesc = NULL;
+		cps->ss.ss_ScanTupleSlot = NULL;
+		cps->ss.ps.ps_ProjInfo = NULL;
+	}
+	/*
+	 * Then, custom-plan provider can have all the own original
+	 * initialization on demand.
+	 */
+	cps->methods->BeginCustomPlan(cps, estate, eflags);
+
+	return cps;
+}
+
+TupleTableSlot *
+ExecCustomPlan(CustomPlanState *cpstate)
+{
+	Assert(cpstate->methods->ExecCustomPlan != NULL);
+	return cpstate->methods->ExecCustomPlan(cpstate);
+}
+
+Node *
+MultiExecCustomPlan(CustomPlanState *cpstate)
+{
+	if (!cpstate->methods->MultiExecCustomPlan)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("CustomPlan \"%s\" does not support MultiExec method",
+						cpstate->methods->CustomName)));
+	return cpstate->methods->MultiExecCustomPlan(cpstate);
+}
+
+void
+ExecEndCustomPlan(CustomPlanState *cpstate)
+{
+	Assert(cpstate->methods->EndCustomPlan != NULL);
+	cpstate->methods->EndCustomPlan(cpstate);
+
+	/* Free the exprcontext */
+	ExecFreeExprContext(&cpstate->ss.ps);
+
+	/* Clean out the tuple table */
+	ExecClearTuple(cpstate->ss.ps.ps_ResultTupleSlot);
+	if (cpstate->ss.ss_ScanTupleSlot)
+		ExecClearTuple(cpstate->ss.ss_ScanTupleSlot);
+
+	/* Close the heap relation, if needed */
+	if (cpstate->ss.ss_currentRelation)
+		ExecCloseScanRelation(cpstate->ss.ss_currentRelation);
+}
+
+void
+ExecReScanCustomPlan(CustomPlanState *cpstate)
+{
+	Assert(cpstate->methods->ReScanCustomPlan != NULL);
+	cpstate->methods->ReScanCustomPlan(cpstate);
+}
+
+void
+ExecCustomMarkPos(CustomPlanState *cpstate)
+{
+	if (!cpstate->methods->MarkPosCustomPlan)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+		errmsg("MarkPos is not supported by custom plan provider: %s",
+			   cpstate->methods->CustomName)));
+	cpstate->methods->MarkPosCustomPlan(cpstate);
+}
+
+void
+ExecCustomRestrPos(CustomPlanState *cpstate)
+{
+	if (!cpstate->methods->RestrPosCustomPlan)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+		errmsg("RestrPos is not supported by custom plan provider: %s",
+			   cpstate->methods->CustomName)));
+	cpstate->methods->RestrPosCustomPlan(cpstate);
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 3088578..b78c6ec 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -598,6 +598,21 @@ _copyForeignScan(const ForeignScan *from)
 }
 
 /*
+ * _copyCustomPlan
+ */
+static CustomPlan *
+_copyCustomPlan(const CustomPlan *from)
+{
+	CustomPlan *newnode;
+
+	newnode = from->methods->CopyCustomPlan(from);
+	Assert(nodeTag(newnode) == nodeTag(from));
+	CopyScanFields((const Scan *) from, (Scan *) newnode);
+
+	return newnode;
+}
+
+/*
  * CopyJoinFields
  *
  *		This function copies the fields of the Join node.  It is used by
@@ -3849,6 +3864,19 @@ _copyAlterTSConfigurationStmt(const AlterTSConfigurationStmt *from)
 	return newnode;
 }
 
+static CreateCustomPlanProviderStmt *
+_copyCreateCustomPlanProviderStmt(const CreateCustomPlanProviderStmt *from)
+{
+	CreateCustomPlanProviderStmt *newnode
+		= makeNode(CreateCustomPlanProviderStmt);
+
+	COPY_STRING_FIELD(cpp_name);
+	COPY_SCALAR_FIELD(cpp_class);
+	COPY_NODE_FIELD(cpp_options);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					pg_list.h copy functions
  * ****************************************************************
@@ -4012,6 +4040,10 @@ copyObject(const void *from)
 		case T_ForeignScan:
 			retval = _copyForeignScan(from);
 			break;
+		case T_CustomPlan:
+		case T_CustomPlanMarkPos:
+			retval = _copyCustomPlan(from);
+			break;
 		case T_Join:
 			retval = _copyJoin(from);
 			break;
@@ -4561,6 +4593,9 @@ copyObject(const void *from)
 		case T_AlterTSConfigurationStmt:
 			retval = _copyAlterTSConfigurationStmt(from);
 			break;
+		case T_CreateCustomPlanProviderStmt:
+			retval = _copyCreateCustomPlanProviderStmt(from);
+			break;
 
 		case T_A_Expr:
 			retval = _copyAExpr(from);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 1b07db6..bdd626e 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2008,6 +2008,17 @@ _equalAlterTSConfigurationStmt(const AlterTSConfigurationStmt *a,
 }
 
 static bool
+_equalCreateCustomPlanProviderStmt(const CreateCustomPlanProviderStmt *a,
+								   const CreateCustomPlanProviderStmt *b)
+{
+	COMPARE_STRING_FIELD(cpp_name);
+	COMPARE_SCALAR_FIELD(cpp_class);
+	COMPARE_NODE_FIELD(cpp_options);
+
+	return true;
+}
+
+static bool
 _equalAExpr(const A_Expr *a, const A_Expr *b)
 {
 	COMPARE_SCALAR_FIELD(kind);
@@ -3025,6 +3036,9 @@ equal(const void *a, const void *b)
 		case T_AlterTSConfigurationStmt:
 			retval = _equalAlterTSConfigurationStmt(a, b);
 			break;
+		case T_CreateCustomPlanProviderStmt:
+			retval = _equalCreateCustomPlanProviderStmt(a, b);
+			break;
 
 		case T_A_Expr:
 			retval = _equalAExpr(a, b);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 9573a9b..e010705 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -564,6 +564,22 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
 }
 
 static void
+_outCustomPlan(StringInfo str, const CustomPlan *node)
+{
+	if (IsA(node, CustomPlan))
+		WRITE_NODE_TYPE("CUSTOMPLAN");
+	else if (IsA(node, CustomPlanMarkPos))
+		WRITE_NODE_TYPE("CUSTOMPLANMARKPOS");
+	else
+		elog(ERROR, "unexpected node tag given: %d", (int)nodeTag(node));
+
+	_outScanInfo(str, (const Scan *) node);
+	appendStringInfo(str, " :methods");
+	_outToken(str, node->methods->CustomName);
+	node->methods->TextOutCustomPlan(str, node);
+}
+
+static void
 _outJoin(StringInfo str, const Join *node)
 {
 	WRITE_NODE_TYPE("JOIN");
@@ -1582,6 +1598,16 @@ _outForeignPath(StringInfo str, const ForeignPath *node)
 }
 
 static void
+_outCustomPath(StringInfo str, const CustomPath *node)
+{
+	WRITE_NODE_TYPE("CUSTOMPATH");
+	_outPathInfo(str, (const Path *) node);
+	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");
@@ -2845,6 +2871,10 @@ _outNode(StringInfo str, const void *obj)
 			case T_ForeignScan:
 				_outForeignScan(str, obj);
 				break;
+			case T_CustomPlan:
+			case T_CustomPlanMarkPos:
+				_outCustomPlan(str, obj);
+				break;
 			case T_Join:
 				_outJoin(str, obj);
 				break;
@@ -3053,6 +3083,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..dbba5ae 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -336,7 +336,7 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 				}
 				break;
 			case RTE_SUBQUERY:
-				/* Subquery --- fully handled during set_rel_size */
+				/* Subquery --- path was added during set_rel_size */
 				break;
 			case RTE_FUNCTION:
 				/* RangeFunction */
@@ -347,12 +347,17 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 				set_values_pathlist(root, rel, rte);
 				break;
 			case RTE_CTE:
-				/* CTE reference --- fully handled during set_rel_size */
+				/* CTE reference --- path was added during set_rel_size */
 				break;
 			default:
 				elog(ERROR, "unexpected rtekind: %d", (int) rel->rtekind);
 				break;
 		}
+		/* Also, consider paths by custom-plan providers */
+		call_custom_scan_providers(root, rel, rte);
+
+		/* Select cheapest path */
+		set_cheapest(rel);
 	}
 
 #ifdef OPTIMIZER_DEBUG
@@ -401,9 +406,6 @@ set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 
 	/* Consider TID scans */
 	create_tidscan_paths(root, rel);
-
-	/* Now find the cheapest of the paths for this rel */
-	set_cheapest(rel);
 }
 
 /*
@@ -429,9 +431,6 @@ set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 {
 	/* Call the FDW's GetForeignPaths function to generate path(s) */
 	rel->fdwroutine->GetForeignPaths(root, rel, rte->relid);
-
-	/* Select cheapest path */
-	set_cheapest(rel);
 }
 
 /*
@@ -1272,9 +1271,6 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
 
 	/* Generate appropriate path */
 	add_path(rel, create_subqueryscan_path(root, rel, pathkeys, required_outer));
-
-	/* Select cheapest path (pretty easy in this case...) */
-	set_cheapest(rel);
 }
 
 /*
@@ -1343,9 +1339,6 @@ set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 	/* Generate appropriate path */
 	add_path(rel, create_functionscan_path(root, rel,
 										   pathkeys, required_outer));
-
-	/* Select cheapest path (pretty easy in this case...) */
-	set_cheapest(rel);
 }
 
 /*
@@ -1366,9 +1359,6 @@ set_values_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 
 	/* Generate appropriate path */
 	add_path(rel, create_valuesscan_path(root, rel, required_outer));
-
-	/* Select cheapest path (pretty easy in this case...) */
-	set_cheapest(rel);
 }
 
 /*
@@ -1435,9 +1425,6 @@ set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 
 	/* Generate appropriate path */
 	add_path(rel, create_ctescan_path(root, rel, required_outer));
-
-	/* Select cheapest path (pretty easy in this case...) */
-	set_cheapest(rel);
 }
 
 /*
@@ -1488,9 +1475,6 @@ set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 
 	/* Generate appropriate path */
 	add_path(rel, create_worktablescan_path(root, rel, required_outer));
-
-	/* Select cheapest path (pretty easy in this case...) */
-	set_cheapest(rel);
 }
 
 /*
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 4b641a2..9939ad8 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -77,13 +77,13 @@ 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 Plan *create_custom_plan(PlannerInfo *root, CustomPath *best_path);
 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,
 					  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);
@@ -261,6 +261,9 @@ create_plan_recurse(PlannerInfo *root, Path *best_path)
 			plan = create_unique_plan(root,
 									  (UniquePath *) best_path);
 			break;
+		case T_CustomPlan:
+			plan = create_custom_plan(root, (CustomPath *) best_path);
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) best_path->pathtype);
@@ -1072,6 +1075,97 @@ 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 'scan_clauses' and targetlist 'tlist'.
+ */
+static Plan *
+create_custom_plan(PlannerInfo *root, CustomPath *best_path)
+{
+	CustomPlan	   *custom_plan;
+	RelOptInfo	   *rel;
+	List		   *tlist = NIL;
+	List		   *clauses = NIL;
+
+	/*
+	 * Create a custom-plan object delivered from CustomPlan type,
+	 * according to the supplied CustomPath
+	 */
+	Assert(best_path->path.pathtype == T_CustomPlan ||
+		   best_path->path.pathtype == T_CustomPlanMarkPos);
+	custom_plan = (CustomPlan *)
+		best_path->methods->CreateCustomPlan(root, best_path);
+	Assert(nodeTag(custom_plan) == best_path->path.pathtype);
+
+	rel = best_path->path.parent;
+	if (rel)
+	{
+		if (rel->reloptkind == RELOPT_BASEREL)
+		{
+			Assert(rel->relid > 0);
+			custom_plan->scan.scanrelid = rel->relid;
+
+			/*
+			 * For table scans, rather than using the relation targetlist
+			 * (which is only those Vars actually needed by the query),
+			 * we prefer to generate a tlist containing all Vars in order.
+			 * This will allow the executor to optimize away projection of
+			 * the table tuples, if possible.
+			 */
+			if (use_physical_tlist(root, rel))
+				tlist = build_physical_tlist(root, rel);
+		}
+		/* elsewhere, we generate a tlist from the relation targetlist */
+		if (tlist == NIL)
+			tlist = build_path_tlist(root, &best_path->path);
+
+		/*
+		 * Extract the relevant restriction clauses from the parent relation.
+		 * The executor must apply all these restrictions during the scan,
+		 * except for pseudoconstants which we'll take care of below.
+		 */
+		clauses = rel->baserestrictinfo;
+
+		/*
+		 * If this is a parameterized scan, we also need to enforce all
+		 * the join clauses available from the outer relation(s).
+		 */
+		if (best_path->path.param_info)
+			clauses = list_concat(list_copy(clauses),
+								  best_path->path.param_info->ppi_clauses);
+
+		/* Sort clauses into best execution order */
+		clauses = order_qual_clauses(root, clauses);
+
+		/*
+		 * Replace outer-relation variables with nestloop params.
+		 * Note that any other clauses which is managed by extension
+		 * itself has to be handled in InitCustomPlan() method, as
+		 * built-in code doing.
+		 */
+		if (best_path->path.param_info)
+			clauses = (List *)replace_nestloop_params(root, (Node *)clauses);
+	}
+	/*
+	 * Copy cost data from Path to Plan; no need to make custom-plan
+	 * providers do this
+	 */
+	copy_path_costsize((Plan *)custom_plan, (Path *)best_path);
+
+	/*
+	 * Let the custom-plan provider perform its final initialization
+	 * of this CustomPlan (to be an inherited type, actually) node
+	 * according to its own necessity.
+	 * Note that custom-plan provider may/can replace (or stack another
+	 * one on) its own custom-plan node on demand, for example, to add
+	 * Result node to handle pseudo constant using create_gating_plan().
+	 */
+	return custom_plan->methods->InitCustomPlan(custom_plan,
+												root, best_path,
+												tlist, clauses);
+}
 
 /*****************************************************************************
  *
@@ -2540,7 +2634,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 4d717df..c416859 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,14 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 			}
 			break;
 
+		case T_CustomPlan:
+			{
+				CustomPlan *cplan = (CustomPlan *) plan;
+
+				cplan->methods->SetCustomPlanRef(root, cplan, rtoffset);
+			}
+			break;
+
 		case T_NestLoop:
 		case T_MergeJoin:
 		case T_HashJoin:
@@ -1158,7 +1165,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..9833073 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -2283,6 +2283,30 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
 			context.paramids = bms_add_members(context.paramids, scan_params);
 			break;
 
+		case T_CustomPlan:
+			{
+				CustomPlan *cplan = (CustomPlan *) plan;
+
+				/*
+				 * If this custom-plan scab a particular relation, we
+				 * adjust paramids as other scan derivered node.
+				 */
+				if (cplan->scan.scanrelid > 0)
+					context.paramids = bms_add_members(context.paramids,
+													   scan_params);
+				/*
+				 * custom plan provider is responsible to apply
+				 * finalize_primnode() on the expression node of its
+				 * private fields, but no need to apply tlist and
+				 * qual of Plan node (already done above).
+				 */
+				if (cplan->methods->FinalizeCustomPlan)
+					cplan->methods->FinalizeCustomPlan(root, cplan,
+													   finalize_primnode,
+													   (void *)&context);
+			}
+			break;
+
 		case T_ModifyTable:
 			{
 				ModifyTable *mtplan = (ModifyTable *) plan;
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index d129f8d..d6beb53 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -16,6 +16,10 @@
 
 #include <math.h>
 
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "catalog/pg_custom_plan_provider.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
@@ -25,8 +29,11 @@
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
+#include "utils/inval.h"
 #include "utils/lsyscache.h"
+#include "utils/memutils.h"
 #include "utils/selfuncs.h"
+#include "utils/syscache.h"
 
 
 typedef enum
@@ -2078,3 +2085,106 @@ reparameterize_path(PlannerInfo *root, Path *path,
 	}
 	return NULL;
 }
+
+/*****************************************************************************
+ *		creation of custom-plan paths
+ *****************************************************************************/
+static List *custom_scan_callchain = NIL;
+static bool custom_plan_callchain_is_ready = false;
+static MemoryContext custom_plan_memcxt = NULL;
+
+static void
+invalidate_custom_plan_callchain(Datum arg, int cacheid, uint32 hashvalue)
+{
+	MemoryContextReset(custom_plan_memcxt);
+	custom_plan_callchain_is_ready = false;
+	custom_scan_callchain = NIL;
+}
+
+static void
+setup_custom_plan_callchain(void)
+{
+	Relation		rel;
+	SysScanDesc		scan;
+	HeapTuple		tuple;
+	MemoryContext	oldcxt;
+
+	custom_scan_callchain = NIL;
+
+	rel = heap_open(CustomPlanProviderRelationId, AccessShareLock);
+
+	/* full scan on the pg_custom_plan once */
+	scan = systable_beginscan(rel, InvalidOid, false, NULL, 0, NULL);
+
+	oldcxt = MemoryContextSwitchTo(custom_plan_memcxt);
+	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+	{
+		Form_pg_custom_plan_provider	cppForm
+			= (Form_pg_custom_plan_provider) GETSTRUCT(tuple);
+
+		if (cppForm->cppclass == CUSTOMPLAN_CLASS_SCAN)
+		{
+			custom_scan_callchain = lappend_oid(custom_scan_callchain,
+												cppForm->cpphandler);
+		}
+		else
+			elog(LOG, "Bug? custom-plan provider \"%s\" has unknown class: %c",
+				 NameStr(cppForm->cppname), cppForm->cppclass);
+	}
+	MemoryContextSwitchTo(oldcxt);
+	systable_endscan(scan);
+
+	heap_close(rel, AccessShareLock);
+
+	custom_plan_callchain_is_ready = true;
+}
+
+static void
+init_custom_plan_callchain(void)
+{
+	/* memory context to keep callchain for custom-plans */
+	custom_plan_memcxt = AllocSetContextCreate(CacheMemoryContext,
+											   "custom plan memory context",
+											   ALLOCSET_DEFAULT_MINSIZE,
+											   ALLOCSET_DEFAULT_INITSIZE,
+											   ALLOCSET_DEFAULT_MAXSIZE);
+
+	/* flush cached callchain on catalog updates */
+	CacheRegisterSyscacheCallback(CUSTOMPLANPROVIDEROID,
+								  invalidate_custom_plan_callchain,
+								  (Datum) 0);
+	/* also, initial setting up */
+	setup_custom_plan_callchain();
+}
+
+/*
+ * call_custom_scan_providers
+ *
+ * A callchain on relation scan. custom-plan provider can add alternative
+ * scan paths derived from CustomPath class.
+ */
+void
+call_custom_scan_providers(PlannerInfo *root,
+						   RelOptInfo *baserel,
+						   RangeTblEntry *rte)
+{
+	customScanArg	sarg;
+	ListCell	   *cell;
+
+	if (!custom_plan_memcxt)
+		init_custom_plan_callchain();
+	else if (!custom_plan_callchain_is_ready)
+		setup_custom_plan_callchain();
+
+	Assert(custom_plan_callchain_is_ready);
+	sarg.custom_class = CUSTOMPLAN_CLASS_SCAN;
+	sarg.root = root;
+	sarg.baserel = baserel;
+	sarg.rte = rte;
+
+	foreach (cell, custom_scan_callchain)
+	{
+		(void) OidFunctionCall1(lfirst_oid(cell),
+								PointerGetDatum(&sarg));
+	}
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a113809..c8ea278 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -51,6 +51,7 @@
 
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/pg_custom_plan_provider.h"
 #include "catalog/pg_trigger.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -259,6 +260,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		DropOwnedStmt ReassignOwnedStmt
 		AlterTSConfigurationStmt AlterTSDictionaryStmt
 		CreateMatViewStmt RefreshMatViewStmt
+		CreateCustomPlanProviderStmt DropCustomPlanProviderStmt
 
 %type <node>	select_no_parens select_with_parens select_clause
 				simple_select values_clause
@@ -514,6 +516,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
 
+%type <ival>	cpp_class
+%type <defelt>	cpp_option
+%type <list>	opt_cpp_options cpp_options
+
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -549,7 +555,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
 	CROSS CSV CURRENT_P
 	CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
-	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
+	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CUSTOM CYCLE
 
 	DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
 	DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DESC
@@ -588,8 +594,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	OBJECT_P OF OFF OFFSET OIDS ON ONLY OPERATOR OPTION OPTIONS OR
 	ORDER ORDINALITY OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER
 
-	PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POSITION
-	PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
+	PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLAN PLANS POSITION
+	PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY PROVIDER
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROGRAM
 
 	QUOTE
@@ -599,8 +605,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK
 	ROW ROWS RULE
 
-	SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
-	SERIALIZABLE SERVER SESSION SESSION_USER SET SETOF SHARE
+	SAVEPOINT SCAN SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE
+	SEQUENCES SERIALIZABLE SERVER SESSION SESSION_USER SET SETOF SHARE
 	SHOW SIMILAR SIMPLE SMALLINT SNAPSHOT SOME STABLE STANDALONE_P START
 	STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBSTRING
 	SYMMETRIC SYSID SYSTEM_P
@@ -762,6 +768,7 @@ stmt :
 			| CreateAssertStmt
 			| CreateCastStmt
 			| CreateConversionStmt
+			| CreateCustomPlanProviderStmt
 			| CreateDomainStmt
 			| CreateExtensionStmt
 			| CreateFdwStmt
@@ -792,6 +799,7 @@ stmt :
 			| DoStmt
 			| DropAssertStmt
 			| DropCastStmt
+			| DropCustomPlanProviderStmt
 			| DropFdwStmt
 			| DropForeignServerStmt
 			| DropGroupStmt
@@ -8705,6 +8713,78 @@ CreateConversionStmt:
 			}
 		;
 
+/****************************************************************************
+ *
+ *	QUERY:
+ *			CREATE CUSTOM PLAN PROVIDER name FOR <class> <options>
+ *
+ ****************************************************************************/
+
+CreateCustomPlanProviderStmt:
+			CREATE CUSTOM PLAN PROVIDER name FOR cpp_class opt_cpp_options
+			{
+				CreateCustomPlanProviderStmt *n
+					= makeNode(CreateCustomPlanProviderStmt);
+				n->cpp_name = $5;
+				n->cpp_class = $7;
+				n->cpp_options = $8;
+				$$ = (Node *) n;
+			}
+		;
+
+cpp_class:
+			SCAN				{ $$ = CUSTOMPLAN_CLASS_SCAN; }
+		;
+
+cpp_option:
+			HANDLER handler_name
+			{
+				$$ = makeDefElem("handler", (Node *)$2);
+			}
+		;
+
+cpp_options:
+			cpp_option					{ $$ = list_make1($1); }
+			| cpp_options cpp_option	{ $$ = lappend($1, $2); }
+		;
+
+opt_cpp_options:
+			cpp_options			{ $$ = $1; }
+			| /* empty */		{ $$ = NIL; }
+		;
+
+/****************************************************************************
+ *
+ *     QUERY:
+ *             DROP CUSTOM PLAN PROVIDER name
+ *
+ ****************************************************************************/
+
+DropCustomPlanProviderStmt:
+			DROP CUSTOM PLAN PROVIDER name opt_drop_behavior
+			{
+				DropStmt *n = makeNode(DropStmt);
+				n->removeType = OBJECT_CPP;
+				n->objects = list_make1(list_make1(makeString($5)));
+				n->arguments = NIL;
+				n->missing_ok = false;
+				n->behavior = $6;
+				n->concurrent = false;
+				$$ = (Node *) n;
+			}
+		|	DROP CUSTOM PLAN PROVIDER IF_P EXISTS name opt_drop_behavior
+			{
+				DropStmt *n = makeNode(DropStmt);
+				n->removeType = OBJECT_CPP;
+				n->objects = list_make1(list_make1(makeString($7)));
+				n->arguments = NIL;
+				n->missing_ok = true;
+				n->behavior = $8;
+				n->concurrent = false;
+				$$ = (Node *) n;
+			}
+		;
+
 /*****************************************************************************
  *
  *		QUERY:
@@ -12914,6 +12994,7 @@ unreserved_keyword:
 			| CSV
 			| CURRENT_P
 			| CURSOR
+			| CUSTOM
 			| CYCLE
 			| DATA_P
 			| DATABASE
@@ -13025,6 +13106,7 @@ unreserved_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PLAN
 			| PLANS
 			| PRECEDING
 			| PREPARE
@@ -13035,6 +13117,7 @@ unreserved_keyword:
 			| PROCEDURAL
 			| PROCEDURE
 			| PROGRAM
+			| PROVIDER
 			| QUOTE
 			| RANGE
 			| READ
@@ -13060,6 +13143,7 @@ unreserved_keyword:
 			| ROWS
 			| RULE
 			| SAVEPOINT
+			| SCAN
 			| SCHEMA
 			| SCROLL
 			| SEARCH
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 07e0b98..cbfd3cf 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -202,6 +202,7 @@ check_xact_readonly(Node *parsetree)
 		case T_AlterTableSpaceOptionsStmt:
 		case T_AlterTableSpaceMoveStmt:
 		case T_CreateForeignTableStmt:
+		case T_CreateCustomPlanProviderStmt:
 		case T_ImportForeignSchemaStmt:
 		case T_SecLabelStmt:
 			PreventCommandIfReadOnly(CreateCommandTag(parsetree));
@@ -684,6 +685,14 @@ standard_ProcessUtility(Node *parsetree,
 			AlterEventTrigger((AlterEventTrigStmt *) parsetree);
 			break;
 
+		case  T_CreateCustomPlanProviderStmt:
+			{
+				CreateCustomPlanProviderStmt *cpp_stmt
+					= (CreateCustomPlanProviderStmt *)parsetree;
+				DefineCustomPlanProvider(cpp_stmt);
+			}
+			break;
+
 			/*
 			 * ******************************** ROLE statements ****
 			 */
@@ -1949,6 +1958,9 @@ CreateCommandTag(Node *parsetree)
 				case OBJECT_OPFAMILY:
 					tag = "DROP OPERATOR FAMILY";
 					break;
+				case OBJECT_CPP:
+					tag = "DROP CUSTOM PLAN PROVIDER";
+					break;
 				default:
 					tag = "???";
 			}
@@ -2216,6 +2228,10 @@ CreateCommandTag(Node *parsetree)
 			tag = "ALTER EVENT TRIGGER";
 			break;
 
+		case T_CreateCustomPlanProviderStmt:
+			tag = "CREATE CUSTOM PLAN PROVIDER";
+			break;
+
 		case T_CreatePLangStmt:
 			tag = "CREATE LANGUAGE";
 			break;
@@ -2840,6 +2856,10 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_CreateCustomPlanProviderStmt:
+			lev = LOGSTMT_DDL;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 0781ac8..abe27e2 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -5481,6 +5481,29 @@ 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-plan provider replaced a varno in expression tree by special
+ * varno.
+ */
+static Node *
+GetSpecialCustomVar(PlanState *ps, Var *varnode, PlanState **child_ps)
+{
+	CustomPlanState *cps = (CustomPlanState *) ps;
+
+	Assert(IsA(ps, CustomPlanState));
+	Assert(IS_SPECIAL_VARNO(varnode->varno));
+
+	if (!cps->methods->GetSpecialCustomVar)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("%s does not support special varno reference",
+						cps->methods->CustomName)));
+	return (Node *)cps->methods->GetSpecialCustomVar(cps, varnode, child_ps);
+}
 
 /*
  * Display a Var appropriately.
@@ -5510,6 +5533,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;
 
@@ -5534,6 +5559,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, CustomPlanState) &&
+			 (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;
@@ -5748,6 +5796,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;
 
@@ -5822,6 +5871,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, CustomPlanState) &&
+			 (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/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 94d951c..1619de7 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -32,6 +32,7 @@
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_conversion.h"
+#include "catalog/pg_custom_plan_provider.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_db_role_setting.h"
 #include "catalog/pg_default_acl.h"
@@ -345,6 +346,28 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		8
 	},
+	{CustomPlanProviderRelationId,	/* CUSTOMPLANPROVIDEROID */
+		CustomPlanProviderOidIndexId,
+		1,
+		{
+			ObjectIdAttributeNumber,
+			0,
+			0,
+			0
+		},
+		32
+	},
+	{CustomPlanProviderRelationId,	/* CUSTOMPLANPROVIDERNAME */
+		CustomPlanProviderNameIndexId,
+		1,
+		{
+			Anum_pg_custom_plan_provider_cppname,
+			0,
+			0,
+			0,
+		},
+		32
+	},
 	{DatabaseRelationId,		/* DATABASEOID */
 		DatabaseOidIndexId,
 		1,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 8ed2592..4a7186a 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -147,6 +147,7 @@ typedef enum ObjectClass
 	OCLASS_DEFACL,				/* pg_default_acl */
 	OCLASS_EXTENSION,			/* pg_extension */
 	OCLASS_EVENT_TRIGGER,		/* pg_event_trigger */
+	OCLASS_CPP,					/* pg_custom_plan_provider */
 	MAX_OCLASS					/* MUST BE LAST */
 } ObjectClass;
 
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 59576fd..b926e15 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -299,6 +299,12 @@ DECLARE_UNIQUE_INDEX(pg_extension_name_index, 3081, on pg_extension using btree(
 DECLARE_UNIQUE_INDEX(pg_range_rngtypid_index, 3542, on pg_range using btree(rngtypid oid_ops));
 #define RangeTypidIndexId					3542
 
+DECLARE_UNIQUE_INDEX(pg_custom_plan_provider_oid_index, 3563, on pg_custom_plan_provider using btree(oid oid_ops));
+#define CustomPlanProviderOidIndexId 3563
+
+DECLARE_UNIQUE_INDEX(pg_custom_plan_provider_name_index, 3564, on pg_custom_plan_provider using btree(cppname name_ops));
+#define CustomPlanProviderNameIndexId 3564
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_custom_plan_provider.h b/src/include/catalog/pg_custom_plan_provider.h
new file mode 100644
index 0000000..f68b4de
--- /dev/null
+++ b/src/include/catalog/pg_custom_plan_provider.h
@@ -0,0 +1,50 @@
+/* -------------------------------------------------------------------------
+ *
+ * pg_custom_plan_provider.h
+ *	definition of the system "custom plan provider" relation
+ *  (pg_custom_plan_provider)
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * -------------------------------------------------------------------------
+ */
+#ifndef PG_CUSTOM_PLAN_PROVIDER_H
+#define PG_CUSTOM_PLAN_PROVIDER_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *     pg_custom_plan_provider definition.  cpp turns this into
+ *     typedef struct FormData_pg_custom_plan_provider
+ * ----------------
+ */
+#define CustomPlanProviderRelationId       3562
+
+CATALOG(pg_custom_plan_provider,3562)
+{
+	NameData	cppname;		/* name of custom-plan provider */
+	char		cppclass;		/* class of custom-plan */
+	Oid			cpphandler;		/* function of custom-plan provider */
+} FormData_pg_custom_plan_provider;
+
+/* ----------------
+ *     Form_pg_custom_plan_provider corresponds to a pointer to a tuple
+ *     with the format of pg_custom_plan_provider relation.
+ * ----------------
+ */
+typedef FormData_pg_custom_plan_provider *Form_pg_custom_plan_provider;
+
+/* ----------------
+ *     compiler constants for pg_custom_plan_provider
+ * ----------------
+ */
+#define Natts_pg_custom_plan_provider			3
+#define Anum_pg_custom_plan_provider_cppname	1
+#define Anum_pg_custom_plan_provider_cppclass	2
+#define Anum_pg_custom_plan_provider_cpphandler	3
+
+/* definition of cppclass */
+#define CUSTOMPLAN_CLASS_SCAN			's'
+
+#endif	/* PG_CUSTOM_PLAN_PROVIDER_H */
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index f8b4a65..fc798a2 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");
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 0ebdbc1..b0f3f0c 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -130,6 +130,11 @@ extern Datum transformGenericOptions(Oid catalogId,
 						List *options,
 						Oid fdwvalidator);
 
+/* commands/custom_plan.c */
+extern Oid get_custom_plan_provider_oid(const char *cpp_name, bool missing_ok);
+extern void	RemoveCustomPlanProviderById(Oid cust_oid);
+extern Oid	DefineCustomPlanProvider(CreateCustomPlanProviderStmt *stmt);
+
 /* support routines in commands/define.c */
 
 extern char *defGetString(DefElem *def);
diff --git a/src/include/executor/nodeCustom.h b/src/include/executor/nodeCustom.h
new file mode 100644
index 0000000..abe1e94
--- /dev/null
+++ b/src/include/executor/nodeCustom.h
@@ -0,0 +1,30 @@
+/* ------------------------------------------------------------------------
+ *
+ * nodeCustom.h
+ *
+ * prototypes for CustomPlan 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 CustomPlanState *ExecInitCustomPlan(CustomPlan *cplan,
+										   EState *estate, int eflags);
+extern TupleTableSlot *ExecCustomPlan(CustomPlanState *node);
+extern Node *MultiExecCustomPlan(CustomPlanState *node);
+extern void ExecEndCustomPlan(CustomPlanState *node);
+
+extern void ExecReScanCustomPlan(CustomPlanState *node);
+extern void ExecCustomMarkPos(CustomPlanState *node);
+extern void ExecCustomRestrPos(CustomPlanState *node);
+
+#endif	/* NODECUSTOM_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index b271f21..26e6b33 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1504,6 +1504,48 @@ typedef struct ForeignScanState
 	void	   *fdw_state;		/* foreign-data wrapper can keep state here */
 } ForeignScanState;
 
+/* ----------------
+ * CustomPlanState information
+ *
+ *		CustomPlan nodes are used to execute custom code within executor.
+ * ----------------
+ */
+struct CustomExecMethods;
+struct ExplainState;	/* to avoid to include explain.h here */
+
+typedef struct CustomPlanState
+{
+	ScanState	ss;
+	const struct CustomExecMethods *methods;
+} CustomPlanState;
+
+typedef struct CustomExecMethods
+{
+	const char     *CustomName;
+
+	/* EXECUTOR methods */
+	void    (*BeginCustomPlan)(CustomPlanState *node,
+							   EState *estate,
+							   int eflags);
+	TupleTableSlot *(*ExecCustomPlan)(CustomPlanState *node);
+	Node   *(*MultiExecCustomPlan)(CustomPlanState *node);
+	void	(*EndCustomPlan)(CustomPlanState *node);
+	void	(*ReScanCustomPlan)(CustomPlanState *node);
+	void	(*MarkPosCustomPlan)(CustomPlanState *node);
+	void	(*RestrPosCustomPlan)(CustomPlanState *node);
+
+	/* EXPLAIN support */
+	void	(*ExplainCustomPlanTargetRel)(CustomPlanState *node,
+										  struct ExplainState *es);
+	void    (*ExplainCustomPlan)(CustomPlanState *node,
+								 List *ancestors,
+								 struct ExplainState *es);
+	void	(*ExplainCustomPreScanNode)(CustomPlanState *node,
+										Bitmapset **rels_used);
+	Node   *(*GetSpecialCustomVar)(CustomPlanState *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 067c768..d5fafbd 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -62,6 +62,8 @@ typedef enum NodeTag
 	T_CteScan,
 	T_WorkTableScan,
 	T_ForeignScan,
+	T_CustomPlan,
+	T_CustomPlanMarkPos,
 	T_Join,
 	T_NestLoop,
 	T_MergeJoin,
@@ -107,6 +109,7 @@ typedef enum NodeTag
 	T_CteScanState,
 	T_WorkTableScanState,
 	T_ForeignScanState,
+	T_CustomPlanState,
 	T_JoinState,
 	T_NestLoopState,
 	T_MergeJoinState,
@@ -224,6 +227,7 @@ typedef enum NodeTag
 	T_HashPath,
 	T_TidPath,
 	T_ForeignPath,
+	T_CustomPath,
 	T_AppendPath,
 	T_MergeAppendPath,
 	T_ResultPath,
@@ -366,6 +370,7 @@ typedef enum NodeTag
 	T_RefreshMatViewStmt,
 	T_ReplicaIdentityStmt,
 	T_AlterSystemStmt,
+	T_CreateCustomPlanProviderStmt,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8364bef..1f98cce 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1209,6 +1209,7 @@ typedef enum ObjectType
 	OBJECT_CONSTRAINT,
 	OBJECT_COLLATION,
 	OBJECT_CONVERSION,
+	OBJECT_CPP,
 	OBJECT_DATABASE,
 	OBJECT_DOMAIN,
 	OBJECT_EVENT_TRIGGER,
@@ -2073,6 +2074,18 @@ typedef struct AlterOpFamilyStmt
 } AlterOpFamilyStmt;
 
 /* ----------------------
+ *     Create Custom Plan Provider Statement
+ * ----------------------
+ */
+typedef struct CreateCustomPlanProviderStmt
+{
+	NodeTag     type;
+	char	   *cpp_name;		/* name of custom-plan provider */
+	char		cpp_class;		/* class of custom-plan provides */
+	List	   *cpp_options;	/* generic options for provider */
+} CreateCustomPlanProviderStmt;
+
+/* ----------------------
  *		Drop Table|Sequence|View|Index|Type|Domain|Conversion|Schema Statement
  * ----------------------
  */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 3b9c683..b48c735 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -15,6 +15,7 @@
 #define PLANNODES_H
 
 #include "access/sdir.h"
+#include "lib/stringinfo.h"
 #include "nodes/bitmapset.h"
 #include "nodes/primnodes.h"
 
@@ -479,6 +480,42 @@ typedef struct ForeignScan
 	bool		fsSystemCol;	/* true if any "system column" is needed */
 } ForeignScan;
 
+/* ----------------
+ *     CustomPlan node
+ * ----------------
+ */
+struct CustomPlanMethods;
+
+struct CustomPath;			/* to avoid to include nodes/relation.h here */
+struct PlannerInfo;			/* to avoid to include nodes/relation.h here */
+
+typedef struct CustomPlan
+{
+	Scan		scan;
+	const struct CustomPlanMethods *methods;
+} CustomPlan;
+
+typedef struct CustomPlanMethods
+{
+	const char *CustomName;
+	Plan	  *(*InitCustomPlan)(CustomPlan *custom_plan,
+								 struct PlannerInfo *root,
+								 struct CustomPath *best_path,
+								 List *tlist,
+								 List *clauses);
+	void	   (*SetCustomPlanRef)(struct PlannerInfo *root,
+								   CustomPlan *custom_plan,
+								   int rtoffset);
+	bool	   (*SupportBackwardScan)(CustomPlan *custom_plan);
+	void	   (*FinalizeCustomPlan)(struct PlannerInfo *root,
+									 CustomPlan *custom_plan,
+									 bool (*finalize_primnode)(),
+									 void *finalize_context);
+	Node	  *(*CreateCustomPlanState)(CustomPlan *custom_plan);
+	void	   (*TextOutCustomPlan)(StringInfo str,
+									const CustomPlan *node);
+	CustomPlan *(*CopyCustomPlan)(const CustomPlan *from);
+} CustomPlanMethods;
 
 /*
  * ==========
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index dacbe9c..a65bb0c 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"
@@ -881,6 +882,31 @@ 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;
+
+typedef struct CustomPath
+{
+	Path        path;
+	const struct CustomPathMethods *methods;
+} CustomPath;
+
+typedef struct CustomPathMethods
+{
+	const char *CustomName;
+	Node   *(*CreateCustomPlan)(PlannerInfo *root,
+								CustomPath *best_path);
+	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..df4d6e8 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -129,6 +129,20 @@ extern Path *reparameterize_path(PlannerInfo *root, Path *path,
 					double loop_count);
 
 /*
+ * interface towards custom-plan provider functions
+ */
+typedef struct {
+	uint32			custom_class;
+	PlannerInfo	   *root;
+	RelOptInfo	   *baserel;
+	RangeTblEntry  *rte;
+} customScanArg;
+
+extern void call_custom_scan_providers(PlannerInfo *root,
+									   RelOptInfo *baserel,
+									   RangeTblEntry *rte);
+
+/*
  * prototypes for relnode.c
  */
 extern void setup_simple_rel_arrays(PlannerInfo *root);
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 8bdb7db..76e3c86 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
@@ -128,6 +129,7 @@ extern List *remove_useless_joins(PlannerInfo *root, List *joinlist);
  */
 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 set_opfuncid(OpExpr *opexpr);
 extern void set_sa_opfuncid(ScalarArrayOpExpr *opexpr);
 extern void record_plan_function_dependency(PlannerInfo *root, Oid funcid);
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index b52e507..05603e5 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -107,6 +107,7 @@ PG_KEYWORD("current_time", CURRENT_TIME, RESERVED_KEYWORD)
 PG_KEYWORD("current_timestamp", CURRENT_TIMESTAMP, RESERVED_KEYWORD)
 PG_KEYWORD("current_user", CURRENT_USER, RESERVED_KEYWORD)
 PG_KEYWORD("cursor", CURSOR, UNRESERVED_KEYWORD)
+PG_KEYWORD("custom", CUSTOM, UNRESERVED_KEYWORD)
 PG_KEYWORD("cycle", CYCLE, UNRESERVED_KEYWORD)
 PG_KEYWORD("data", DATA_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("database", DATABASE, UNRESERVED_KEYWORD)
@@ -282,6 +283,7 @@ PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD)
 PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD)
+PG_KEYWORD("plan", PLAN, UNRESERVED_KEYWORD)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD)
 PG_KEYWORD("position", POSITION, COL_NAME_KEYWORD)
 PG_KEYWORD("preceding", PRECEDING, UNRESERVED_KEYWORD)
@@ -295,6 +297,7 @@ PG_KEYWORD("privileges", PRIVILEGES, UNRESERVED_KEYWORD)
 PG_KEYWORD("procedural", PROCEDURAL, UNRESERVED_KEYWORD)
 PG_KEYWORD("procedure", PROCEDURE, UNRESERVED_KEYWORD)
 PG_KEYWORD("program", PROGRAM, UNRESERVED_KEYWORD)
+PG_KEYWORD("provider", PROVIDER, UNRESERVED_KEYWORD)
 PG_KEYWORD("quote", QUOTE, UNRESERVED_KEYWORD)
 PG_KEYWORD("range", RANGE, UNRESERVED_KEYWORD)
 PG_KEYWORD("read", READ, UNRESERVED_KEYWORD)
@@ -325,6 +328,7 @@ PG_KEYWORD("row", ROW, COL_NAME_KEYWORD)
 PG_KEYWORD("rows", ROWS, UNRESERVED_KEYWORD)
 PG_KEYWORD("rule", RULE, UNRESERVED_KEYWORD)
 PG_KEYWORD("savepoint", SAVEPOINT, UNRESERVED_KEYWORD)
+PG_KEYWORD("scan", SCAN, UNRESERVED_KEYWORD)
 PG_KEYWORD("schema", SCHEMA, UNRESERVED_KEYWORD)
 PG_KEYWORD("scroll", SCROLL, UNRESERVED_KEYWORD)
 PG_KEYWORD("search", SEARCH, UNRESERVED_KEYWORD)
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index f97229f..e1e56f7 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -52,6 +52,8 @@ enum SysCacheIdentifier
 	CONNAMENSP,
 	CONSTROID,
 	CONVOID,
+	CUSTOMPLANPROVIDEROID,
+	CUSTOMPLANPROVIDERNAME,
 	DATABASEOID,
 	DEFACLROLENSPOBJ,
 	ENUMOID,
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 111d24c..e3cad45 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -97,6 +97,7 @@ pg_class|t
 pg_collation|t
 pg_constraint|t
 pg_conversion|t
+pg_custom_plan|t
 pg_database|t
 pg_db_role_setting|t
 pg_default_acl|t
#9Shigeru Hanada
shigeru.hanada@gmail.com
In reply to: Kohei KaiGai (#8)

Kaigai-san,

2014-07-14 22:18 GMT+09:00 Kohei KaiGai <kaigai@kaigai.gr.jp>:

Hanada-san,

Thanks for your checking. The attached v4 patch is rebased one on the
latest master branch. Indeed, merge conflict was trivial.

Updates from the v3 are below:
- custom-plan.sgml was renamed to custom-plan-provider.sgml
- fix up the comments in pg_custom_plan_provider.h that mentioned
about old field name.
- applied your patch to fix up typos. (thanks so much!)
- put "costs off" on the EXPLAIN command in the regression test of
ctidscan extension.

Checked, but the patch fails sanity-check test, you need to modify
expected file of the test.

Nothing to comment on the design and implementation from your
viewpoint any more?

As much as I can tell, the design seems reasonable. After fix for the
small issue above, I'll move the patch status to "Ready for
committer".

--
Shigeru HANADA

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

#10Kouhei Kaigai
kaigai@ak.jp.nec.com
In reply to: Shigeru Hanada (#9)
1 attachment(s)

2014-07-14 22:18 GMT+09:00 Kohei KaiGai <kaigai@kaigai.gr.jp>:

Hanada-san,

Thanks for your checking. The attached v4 patch is rebased one on the
latest master branch. Indeed, merge conflict was trivial.

Updates from the v3 are below:
- custom-plan.sgml was renamed to custom-plan-provider.sgml
- fix up the comments in pg_custom_plan_provider.h that mentioned
about old field name.
- applied your patch to fix up typos. (thanks so much!)
- put "costs off" on the EXPLAIN command in the regression test of
ctidscan extension.

Checked, but the patch fails sanity-check test, you need to modify expected
file of the test.

Sorry, expected result of sanity-check test was not updated on
renaming to pg_custom_plan_provider.
The attached patch fixed up this point.

Nothing to comment on the design and implementation from your
viewpoint any more?

As much as I can tell, the design seems reasonable. After fix for the small
issue above, I'll move the patch status to "Ready for committer".

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

Attachments:

pgsql-v9.5-custom-plan.v5.patchapplication/octet-stream; name=pgsql-v9.5-custom-plan.v5.patchDownload
 contrib/Makefile                                  |   1 +
 contrib/ctidscan/Makefile                         |  19 +
 contrib/ctidscan/ctidscan--1.0.sql                |  12 +
 contrib/ctidscan/ctidscan--unpackaged-1.0.sql     |   0
 contrib/ctidscan/ctidscan.c                       | 952 ++++++++++++++++++++++
 contrib/ctidscan/ctidscan.control                 |   5 +
 contrib/ctidscan/expected/ctidscan.out            | 312 +++++++
 contrib/ctidscan/sql/ctidscan.sql                 |  54 ++
 doc/src/sgml/catalogs.sgml                        |  59 ++
 doc/src/sgml/custom-plan-provider.sgml            | 431 ++++++++++
 doc/src/sgml/filelist.sgml                        |   1 +
 doc/src/sgml/postgres.sgml                        |   1 +
 doc/src/sgml/ref/allfiles.sgml                    |   2 +
 doc/src/sgml/ref/create_custom_plan_provider.sgml | 139 ++++
 doc/src/sgml/ref/drop_custom_plan_provider.sgml   | 108 +++
 doc/src/sgml/reference.sgml                       |   2 +
 src/backend/catalog/Makefile                      |   2 +-
 src/backend/catalog/dependency.c                  |  11 +-
 src/backend/catalog/objectaddress.c               |  63 ++
 src/backend/commands/Makefile                     |   2 +-
 src/backend/commands/custom_plan.c                | 188 +++++
 src/backend/commands/dropcmds.c                   |   5 +
 src/backend/commands/event_trigger.c              |   2 +
 src/backend/commands/explain.c                    |  40 +
 src/backend/executor/Makefile                     |   2 +-
 src/backend/executor/execAmi.c                    |  25 +
 src/backend/executor/execProcnode.c               |  19 +
 src/backend/executor/nodeCustom.c                 | 147 ++++
 src/backend/nodes/copyfuncs.c                     |  35 +
 src/backend/nodes/equalfuncs.c                    |  14 +
 src/backend/nodes/outfuncs.c                      |  33 +
 src/backend/optimizer/path/allpaths.c             |  30 +-
 src/backend/optimizer/plan/createplan.c           |  98 ++-
 src/backend/optimizer/plan/setrefs.c              |  11 +-
 src/backend/optimizer/plan/subselect.c            |  24 +
 src/backend/optimizer/util/pathnode.c             | 110 +++
 src/backend/parser/gram.y                         |  94 ++-
 src/backend/tcop/utility.c                        |  20 +
 src/backend/utils/adt/ruleutils.c                 |  73 ++
 src/backend/utils/cache/syscache.c                |  23 +
 src/include/catalog/dependency.h                  |   1 +
 src/include/catalog/indexing.h                    |   6 +
 src/include/catalog/pg_custom_plan_provider.h     |  50 ++
 src/include/catalog/pg_operator.h                 |   3 +
 src/include/commands/defrem.h                     |   5 +
 src/include/executor/nodeCustom.h                 |  30 +
 src/include/nodes/execnodes.h                     |  42 +
 src/include/nodes/nodes.h                         |   5 +
 src/include/nodes/parsenodes.h                    |  13 +
 src/include/nodes/plannodes.h                     |  37 +
 src/include/nodes/relation.h                      |  26 +
 src/include/optimizer/pathnode.h                  |  14 +
 src/include/optimizer/planmain.h                  |   2 +
 src/include/parser/kwlist.h                       |   4 +
 src/include/utils/syscache.h                      |   2 +
 src/test/regress/expected/sanity_check.out        |   1 +
 56 files changed, 3374 insertions(+), 36 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..1e476a6
--- /dev/null
+++ b/contrib/ctidscan/Makefile
@@ -0,0 +1,19 @@
+# contrib/ctidscan/Makefile
+
+MODULES = ctidscan
+
+EXTENSION = ctidscan
+DATA = ctidscan--1.0.sql
+
+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--1.0.sql b/contrib/ctidscan/ctidscan--1.0.sql
new file mode 100644
index 0000000..46420a0
--- /dev/null
+++ b/contrib/ctidscan/ctidscan--1.0.sql
@@ -0,0 +1,12 @@
+--
+-- Create ctidscan handler function
+--
+CREATE FUNCTION ctidscanaddpath(internal)
+  RETURNS pg_catalog.void
+  AS 'MODULE_PATHNAME','CtidScanAddPath'
+  LANGUAGE C STRICT;
+
+--
+-- Create a custom-plan provider
+--
+CREATE CUSTOM PLAN PROVIDER ctidscan FOR scan HANDLER ctidscanaddpath;
diff --git a/contrib/ctidscan/ctidscan--unpackaged-1.0.sql b/contrib/ctidscan/ctidscan--unpackaged-1.0.sql
new file mode 100644
index 0000000..e69de29
diff --git a/contrib/ctidscan/ctidscan.c b/contrib/ctidscan/ctidscan.c
new file mode 100644
index 0000000..5151365
--- /dev/null
+++ b/contrib/ctidscan/ctidscan.c
@@ -0,0 +1,952 @@
+/*
+ * 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_custom_plan_provider.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 {
+	CustomPlan		cplan;
+	List		   *ctid_quals;
+} CtidScanPlan;
+
+typedef struct {
+	CustomPlanState	cps;
+	List		   *ctid_quals;		/* list of ExprState for inequality ops */
+} CtidScanState;
+
+static CustomPathMethods	ctidscan_path_methods;
+static CustomPlanMethods	ctidscan_plan_methods;
+static CustomExecMethods	ctidscan_exec_methods;
+
+/* function declarations */
+void	_PG_init(void);
+Datum	CtidScanAddPath(PG_FUNCTION_ARGS);
+
+#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;
+}
+
+/*
+ * CreateCtidScanPlan - A method of CustomPath; that populate a custom
+ * object being delivered from CustomPlan type, according to the supplied
+ * CustomPath object.
+ */
+static Node *
+CreateCtidScanPlan(PlannerInfo *root, CustomPath *best_path)
+{
+	CtidScanPath   *ctid_path = (CtidScanPath *) best_path;
+	CtidScanPlan   *ctid_scan;
+
+	ctid_scan = palloc0(sizeof(CtidScanPlan));
+	NodeSetTag(ctid_scan, T_CustomPlan);
+	ctid_scan->cplan.methods = &ctidscan_plan_methods;
+	ctid_scan->ctid_quals = ctid_path->ctid_quals;
+
+	return (Node *)&ctid_scan->cplan;
+}
+
+/*
+ * InitCtidScanPlan
+ *
+ * main portion to initialize CustomPlan node.
+ */
+static Plan *
+InitCtidScanPlan(CustomPlan *custom_plan,
+				 PlannerInfo *root, CustomPath *best_path,
+				 List *tlist, List *clauses)
+{
+	CtidScanPlan   *ctid_scan = (CtidScanPlan *)custom_plan;
+	List		   *ctid_quals = ((CtidScanPath *)best_path)->ctid_quals;
+
+	Assert(ctid_scan->cplan.scan.scanrelid > 0);
+
+	/* Set targetlist as is */
+	ctid_scan->cplan.scan.plan.targetlist = tlist;
+	/* Reduce RestrictInfo list to bare expressions */
+	ctid_scan->cplan.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 &ctid_scan->cplan.scan.plan;
+}
+
+/*
+ * 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 CustomPlan; 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,
+				   CustomPlan *custom_plan,
+				   int rtoffset)
+{
+	CtidScanPlan   *ctidscan = (CtidScanPlan *) custom_plan;
+	Scan		   *scan = &ctidscan->cplan.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);
+}
+
+/*
+ * SupportCtidBackwardScan - A method of CustomPlan; that informs the core
+ * backend whether this custom-plan node support backward scan or not.
+ */
+static bool
+SupportCtidBackwardScan(CustomPlan *custom_plan)
+{
+	return true;
+}
+
+/*
+ * FinalizeCtidScanPlan - A method of CustomPlan; that handles callbacks
+ * by finalize_plan().
+ */
+static void
+FinalizeCtidScanPlan(PlannerInfo *root,
+					 CustomPlan *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 CustomPlan; that populate a custom
+ * object being delivered from CustomPlanState type, according to the
+ * supplied CustomPath object.
+ */
+static Node *
+CreateCtidScanState(CustomPlan *custom_plan)
+{
+	CtidScanState  *ctss = palloc0(sizeof(CtidScanState));
+
+	NodeSetTag(ctss, T_CustomPlanState);
+	ctss->cps.methods = &ctidscan_exec_methods;
+
+	return (Node *)&ctss->cps;
+}
+
+/*
+ * TextOutCtidScanPlan - A method of CustomPlan; that generates text
+ * representation of the given object.
+ */
+static void
+TextOutCtidScanPlan(StringInfo str, const CustomPlan *node)
+{
+	CtidScanPlan   *ctid_plan = (CtidScanPlan *) node;
+
+	appendStringInfo(str, " :ctid_quals %s",
+					 nodeToString(ctid_plan->ctid_quals));
+}
+
+/*
+ * CopyCtidScanPlan - A method of CustomPlan; that create a copied object.
+ */
+static CustomPlan *
+CopyCtidScanPlan(const CustomPlan *from)
+{
+	CtidScanPlan   *oldnode = (CtidScanPlan *) from;
+	CtidScanPlan   *newnode = palloc0(sizeof(CtidScanPlan));
+
+	NodeSetTag(newnode, T_CustomPlan);
+	newnode->cplan.methods = oldnode->cplan.methods;
+	newnode->ctid_quals = copyObject(oldnode->ctid_quals);
+
+	return &newnode->cplan;
+}
+
+/*
+ * BeginCtidScan - A method of CustomPlanState; that initializes
+ * the supplied CtidScanState object, at beginning of the executor.
+ */
+static void
+BeginCtidScan(CustomPlanState *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->cps.ss.ss_currentScanDesc = NULL;
+}
+
+/*
+ * ReScanCtidScan - A method of CustomPlanState; that rewind the current
+ * seek position.
+ */
+static void
+ReScanCtidScan(CustomPlanState *node)
+{
+	CtidScanState  *ctss = (CtidScanState *)node;
+	HeapScanDesc	scan = ctss->cps.ss.ss_currentScanDesc;
+	EState		   *estate = node->ss.ps.state;
+	ScanDirection	direction = estate->es_direction;
+	Relation		relation = ctss->cps.ss.ss_currentRelation;
+	ExprContext	   *econtext = ctss->cps.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->cps.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->cps.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->cps.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(CustomPlanState *node)
+{
+	CtidScanState  *ctss = (CtidScanState *) node;
+	HeapScanDesc	scan;
+	TupleTableSlot *slot;
+	EState		   *estate = node->ss.ps.state;
+	ScanDirection	direction = estate->es_direction;
+	HeapTuple		tuple;
+
+	if (!ctss->cps.ss.ss_currentScanDesc)
+		ReScanCtidScan(node);
+	scan = ctss->cps.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->cps.ss.ss_ScanTupleSlot;
+	ExecStoreTuple(tuple, slot, scan->rs_cbuf, false);
+
+	return slot;
+}
+
+static bool
+CTidRecheckCustomScan(CustomPlanState *node, TupleTableSlot *slot)
+{
+	return true;
+}
+
+/*
+ * ExecCtidScan - A method of CustomPlanState; that fetches a tuple
+ * from the relation, if exist anymore.
+ */
+static TupleTableSlot *
+ExecCtidScan(CustomPlanState *node)
+{
+	return ExecScan(&node->ss,
+					(ExecScanAccessMtd) CTidAccessCustomScan,
+					(ExecScanRecheckMtd) CTidRecheckCustomScan);
+}
+
+/*
+ * CTidEndCustomScan - A method of CustomPlanState; that closes heap and
+ * scan descriptor, and release other related resources.
+ */
+static void
+EndCtidScan(CustomPlanState *node)
+{
+	CtidScanState  *ctss = (CtidScanState *)node;
+
+	if (ctss->cps.ss.ss_currentScanDesc)
+		heap_endscan(ctss->cps.ss.ss_currentScanDesc);
+}
+
+/*
+ * ExplanCtidScanTargetRel - A method of CustomPlanState; that output
+ * relation's name to be scanned.
+ */
+static void
+ExplanCtidScanTargetRel(CustomPlanState *node, ExplainState *es)
+{
+	CtidScanState  *ctss = (CtidScanState *) node;
+	CtidScanPlan   *ctid_plan = (CtidScanPlan *) ctss->cps.ss.ps.plan;
+	Index			rti = ctid_plan->cplan.scan.scanrelid;
+	RangeTblEntry   *rte;
+	char		   *objectname = NULL;
+	char		   *namespace = NULL;
+	char		   *refname;
+
+	/* logic copied from ExplainTargetRel */
+	rte = rt_fetch(rti, es->rtable);
+	refname = (char *) list_nth(es->rtable_names, rti - 1);
+	if (refname == NULL)
+		refname = rte->eref->aliasname;
+
+	Assert(rte->rtekind == RTE_RELATION);
+	objectname = get_rel_name(rte->relid);
+	if (es->verbose)
+		namespace = get_namespace_name(get_rel_namespace(rte->relid));
+
+	if (es->format == EXPLAIN_FORMAT_TEXT)
+	{
+		appendStringInfoString(es->str, " on");
+		if (namespace != NULL)
+			appendStringInfo(es->str, " %s.%s", quote_identifier(namespace),
+							 quote_identifier(objectname));
+		else if (objectname != NULL)
+			appendStringInfo(es->str, " %s", quote_identifier(objectname));
+		if (objectname == NULL || strcmp(refname, objectname) != 0)
+			appendStringInfo(es->str, " %s", quote_identifier(refname));
+	}
+	else
+	{
+		if (objectname != NULL)
+			ExplainPropertyText("Relation Name", objectname, es);
+		if (namespace != NULL)
+			ExplainPropertyText("Schema", namespace, es);
+		ExplainPropertyText("Alias", refname, es);
+	}
+}
+
+/*
+ * ExplainCtidScan - A method of CustomPlanState; that shows extra info
+ * on EXPLAIN command.
+ */
+static void
+ExplainCtidScan(CustomPlanState *node, List *ancestors, ExplainState *es)
+{
+	CtidScanState  *ctss = (CtidScanState *) node;
+	CtidScanPlan   *ctid_plan = (CtidScanPlan *) ctss->cps.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);
+	}
+}
+
+/*
+ * ExplainCtidPreScanNode - A method of CustomPlanState; that informs
+ * the core backend relation's rtindex to be referenced, prior to the
+ * main EXPLAIN processing.
+ */
+static void
+ExplainCtidPreScanNode(CustomPlanState *node, Bitmapset **rels_used)
+{
+	CtidScanState  *ctss = (CtidScanState *) node;
+	Index			scanrelid = ((Scan *)ctss->cps.ss.ps.plan)->scanrelid;
+
+	*rels_used = bms_add_member(*rels_used, scanrelid);
+}
+
+/*
+ * Entrypoint of this extension
+ */
+Datum
+CtidScanAddPath(PG_FUNCTION_ARGS)
+{
+	customScanArg  *cscan_arg = (customScanArg *)PG_GETARG_POINTER(0);
+	PlannerInfo	   *root;
+	RangeTblEntry  *rte;
+	RelOptInfo	   *baserel;
+	char			relkind;
+	ListCell	   *lc;
+	List		   *ctid_quals = NIL;
+
+	if (cscan_arg->custom_class != CUSTOMPLAN_CLASS_SCAN)
+		PG_RETURN_VOID();
+
+	root = cscan_arg->root;
+	rte = cscan_arg->rte;
+	baserel = cscan_arg->baserel;
+
+	/* all we can support is regular relations */
+	if (rte->rtekind != RTE_RELATION)
+		PG_RETURN_VOID();
+
+	relkind = get_rel_relkind(rte->relid);
+	if (relkind != RELKIND_RELATION &&
+		relkind != RELKIND_MATVIEW &&
+		relkind != RELKIND_TOASTVALUE)
+		PG_RETURN_VOID();
+
+	/* 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_CustomPlan;
+		ctid_path->cpath.path.parent = baserel;
+		ctid_path->cpath.path.param_info
+			= get_baserel_parampathinfo(root, baserel, required_outer);
+		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);
+	}
+	PG_RETURN_VOID();
+}
+PG_FUNCTION_INFO_V1(CtidScanAddPath);
+
+/*
+ * Entrypoint of this extension
+ */
+void
+_PG_init(void)
+{
+	/* setup ctidscan_path_methods */
+	ctidscan_path_methods.CustomName = "ctidscan";
+	ctidscan_path_methods.CreateCustomPlan = CreateCtidScanPlan;
+	ctidscan_path_methods.TextOutCustomPath = TextOutCtidScanPath;
+
+	/* setup ctidscan_plan_methods */
+	ctidscan_plan_methods.CustomName = "ctidscan";
+	ctidscan_plan_methods.InitCustomPlan = InitCtidScanPlan;
+	ctidscan_plan_methods.SetCustomPlanRef = SetCtidScanPlanRef;
+	ctidscan_plan_methods.SupportBackwardScan = SupportCtidBackwardScan;
+	ctidscan_plan_methods.FinalizeCustomPlan = FinalizeCtidScanPlan;
+	ctidscan_plan_methods.CreateCustomPlanState = CreateCtidScanState;
+	ctidscan_plan_methods.TextOutCustomPlan = TextOutCtidScanPlan;
+	ctidscan_plan_methods.CopyCustomPlan = CopyCtidScanPlan;
+
+	/* setup ctidscan_planstate_methods */
+	ctidscan_exec_methods.CustomName = "ctidscan";
+	ctidscan_exec_methods.BeginCustomPlan = BeginCtidScan;
+	ctidscan_exec_methods.ExecCustomPlan = ExecCtidScan;
+	ctidscan_exec_methods.EndCustomPlan = EndCtidScan;
+	ctidscan_exec_methods.ReScanCustomPlan = ReScanCtidScan;
+	ctidscan_exec_methods.MarkPosCustomPlan = NULL;
+	ctidscan_exec_methods.RestrPosCustomPlan = NULL;
+	ctidscan_exec_methods.ExplainCustomPlanTargetRel = ExplanCtidScanTargetRel;
+	ctidscan_exec_methods.ExplainCustomPlan = ExplainCtidScan;
+	ctidscan_exec_methods.ExplainCustomPreScanNode = ExplainCtidPreScanNode;
+	ctidscan_exec_methods.GetSpecialCustomVar = NULL;
+}
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..74d66e6
--- /dev/null
+++ b/contrib/ctidscan/expected/ctidscan.out
@@ -0,0 +1,312 @@
+--
+-- Regression Tests for Custom Plan APIs
+--
+CREATE EXTENSION ctidscan;
+-- 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                              
+----------------------------------------------------------------------
+ Custom (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 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 (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 (ctidscan) on t1
+               Filter: (ctid < '(2,10)'::tid)
+               ctid quals: (ctid < '(2,10)'::tid)
+   ->  Sort
+         Sort Key: t2.ctid
+         ->  Custom (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 (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 (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)
+
+-- 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..213a97a
--- /dev/null
+++ b/contrib/ctidscan/sql/ctidscan.sql
@@ -0,0 +1,54 @@
+--
+-- Regression Tests for Custom Plan APIs
+--
+
+CREATE EXTENSION ctidscan;
+
+-- 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;
+
+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);
+
+-- Test cleanup
+DROP SCHEMA regtest_custom_scan CASCADE;
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index b4a06e4..3d7a289 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -109,6 +109,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-custom-plan-provider"><structname>pg_custom_plan_provider</structname></link></entry>
+      <entry>custom plan providers</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-conversion"><structname>pg_conversion</structname></link></entry>
       <entry>encoding conversion information</entry>
      </row>
@@ -2508,6 +2513,60 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-custom-plan-provider">
+  <title><structname>pg_custom_plan_provider</structname></title>
+
+  <indexterm zone="catalog-pg-custom-plan-provider">
+   <primary>pg_custom_plan_provider</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_custom_plan_provider</structname> describes
+   custom-plan providers. See <xref linkend="sql-createcustomplanprovider">
+   for more information.
+  </para>
+
+  <table>
+   <title><structname>pg_custom_plan_provider</> Columns</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>References</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><structfield>oid</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry></entry>
+      <entry>Row identifier (hidden attribute; must be explicitly selected)</entry>
+     </row>
+     <row>
+      <entry><structfield>cppname</structfield></entry>
+      <entry><type>name</type></entry>
+      <entry></entry>
+      <entry>custom-plan provider name</entry>
+     </row>
+     <row>
+      <entry><structfield>cppclass</structfield></entry>
+      <entry><type>char</type></entry>
+      <entry></entry>
+      <entry>class of custom-plan node on which this custom-plan provider can perform</entry>
+     </row>
+     <row>
+      <entry><structfield>cpphandler</structfield></entry>
+      <entry><type>regproc</type></entry>
+      <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
+      <entry>handler function of custom-plan provder that will propose an alternative query execution path</entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
  <sect1 id="catalog-pg-database">
   <title><structname>pg_database</structname></title>
 
diff --git a/doc/src/sgml/custom-plan-provider.sgml b/doc/src/sgml/custom-plan-provider.sgml
new file mode 100644
index 0000000..d86495b
--- /dev/null
+++ b/doc/src/sgml/custom-plan-provider.sgml
@@ -0,0 +1,431 @@
+<!-- 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 variable 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 the extension that provides custom-plan custom plan
+  provider.
+ </para>
+
+ <sect1 id="cpp-overall-steps">
+  <title>Custom Plan Overall Steps</title>
+  <para>
+   A custom plan provider can be registered using
+   <xref linkend="sql-createcustomplanprovider"> command.
+   It takes a class of custom plan and its handler function.
+   Class of custom plan specifies the task to be replaced by the custom-
+   plan being defined.
+   Only <literal>SCAN</> is the supported class of custom-plan right now.
+   Custom plan handler function is an entrypoint to the planner. It calls
+   the handler function during construction of query execution path.
+  </para>
+  <para>
+   Handler function has to be declared as a function that takes one
+   <literal>internal</> data type and returns <literal>void</>.
+   On invocation, query planner delivers a pointer of data structure
+   according to the custom plan class, custom-plan handler function will
+   decide whether it can offer alternative execution path towards the
+   required task. If available, it can add a <literal>CustomPath</>
+   (or inherited object type) as one of the candidate execution path
+   with estimated cost and set of callbacks defined in the
+   <literal>CustomPathMethods</> structure.
+  </para>
+  <para>
+   Planner compares all the potential execution paths based on its cost.
+   Once a custom path that was added by custom plan provider gets chosen,
+   <literal>CreateCustomPlan</> callback shall be called, to populate
+   a <literal>CustomPlan</> (or inherited object type) node according to
+   the <literal>CustomPath</> node preliminary constructed.
+   In the similar manner, <literal>CreateCustomPlanState</> callback shall
+   be called, to populate a <literal>CustomPlanState</> (or inherited
+   object type) node according to the <literal>CustomPlan</> node being
+   constructed above.
+   The reason why custom-plan provider has to allocate a node object by
+   itself is that we allow to extend the base types to store private
+   fields managed by individual custom-plan providers, thus only custom-
+   plan provider knows actual data size to be allocated.
+  </para>
+  <para>
+   Once a <literal>CustomPlanState</> is constructed, its callback
+   functions are invoked by executor, so custom-plan provider performs
+   required tasks then pops up the result.
+  </para>
+ </sect1>
+
+ <sect1 id="cpp-handler-function">
+  <title>Custom Plan Handler Functions</title>
+  <para>
+   A handler function that was specified on
+   the <xref linkend="sql-createcustomplanprovider"> command is
+   declared to return <literal>void</> data type and takes an
+   <literal>internal</> data type; that is usually applied to exchange
+   internal data structure.
+   This handler function is not an exception. It can reference a pointer
+   being informed via function argument to understand the context.
+  </para>
+  <para>
+   The data structure of arguments fully depend on the class of custom-
+   plan. In case of <literal>SCAN</> class (that is only supported one
+   right now), the first argument points the following data structure
+   that has enough information to construct a <literal>Path</> node.
+<programlisting>
+typedef struct {
+    uint32          cpp_class;
+    PlannerInfo    *root;
+    RelOptInfo     *baserel;
+    RangeTblEntry  *rte;
+} customScanArg;
+</programlisting>
+   <literal>cpp_class</>is always <literal>CUSTOMPLAN_CLASS_SCAN</>
+   that shows this structure is for scan class.
+   <literal>root</> is <literal>PlannerInfo</> of this plan,
+   <literal>baserel</> is <literal>RelOptInfo</> of the relation to
+   be scanned, and <literal>rte</> is <literal>RangeTblEntry</> of
+   the relation.
+  </para>
+  <para>
+   The custom-plan provider being invoked can check whether it can
+   provides alternative way to scan the relation. If available, it
+   shall construct <literal>CustomPath</> or its inherited one with
+   estimated cost and callbacks below, then register the path using
+   <literal>add_path</> towards the supplied <literal>RelOptInfo</>.
+  </para>
+ </sect1>
+
+ <sect1 id="cpp-path-callbacks">
+  <title>Custom Path Callbacks</title>
+  <para>
+   This section introduces callback functions of <literal>CustomPath</>
+   structure; defined in the <literal>CustomPathMethods</>.
+  </para>
+  <para>
+<programlisting>
+Node *
+CreateCustomPlan(PlannerInfo *root,
+                 CustomPath *best_path);
+</programlisting>
+   It populates a <literal>CustomPlan</> (or inherited data type) node
+   according to the supplied <literal>CustomPath</> node which was
+   constructed on the custom plan handler function then chosen by the
+   query planner.
+   Only custom plan provider can know exact size of the node to be
+   allocated, this callback allocate a <literal>CustomPlan</> node with
+   <literal>CustomPlanMethods</> callbacks table and arbitrary private
+   fields.
+  </para>
+  <para>
+   Note that the node tag can be assigned is either <literal>CustomPlan</>
+   or <literal>CustomPlanMarkPos</>. The later one indicates this plan
+   node supports mark and restore position during query execution, but
+   has identical data structure.
+  </para>
+
+
+  <para>
+<programlisting>
+void
+TextOutCustomPath(StringInfo str, const CustomPath *node);
+</programlisting>
+   It makes a text representation of custom path node. If custom-plan
+   provider extends <literal>CustomPath</> data type, it shall to put private
+   fields on the supplied <literal>StringInfo</> with text form.
+   Note that common fields in <literal>CustomPath</> are handled by backend,
+   so extension needs to do nothing special.
+  </para>
+ </sect1>
+
+ <sect1 id="cpp-plan-callbacks">
+  <title>Custom Plan Callbacks</title>
+  <para>
+   This section introduces callback functions of <literal>CustomPlan</>
+   structure; defined in the <literal>CustomPlanMethods</>.
+  </para>
+  <para>
+<programlisting>
+Plan      *
+InitCustomPlan(CustomPlan *custom_plan,
+               PlannerInfo *root,
+               CustomPath *best_path,
+               List *tlist,
+               List *clauses);
+</programlisting>
+   It initializes the <literal>CustomPlan</> node being acquired by
+   <literal>CreateCustomPlan</> callback.
+   The backend takes some common initializations prior to its invocation.
+   <literal>tlist</> and <literal>clauses</> are extracted from the path
+   node according to the usual manner, so all custom plan provider has to
+   do is putting these members if nothing special are done.
+  </para>
+
+  <para>
+<programlisting>
+void
+SetCustomPlanRef(PlannerInfo *root,
+                 CustomPlan *custom_plan,
+                 int rtoffset);
+</programlisting>
+   It adjusts <literal>varno</> and <literal>varattno</> of var-nodes in
+   the expression tree chained from <literal>CustomPlan</> 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 <literal>fix_scan_expr</> shall be called on expression nodes to
+   track plan dependency.
+  </para>
+
+  <para>
+<programlisting>
+bool
+SupportBackwardScan(CustomPlan *custom_plan);
+</programlisting>
+   It is an optional callback, which tells the backend whether this custom-
+   plan node supports backward scan or not. If <literal>NULL</>, it means
+   backward scan is not supported.
+  </para>
+
+  <para>
+<programlisting>
+void
+FinalizeCustomPlan(PlannerInfo *root,
+                   CustomPlan *custom_plan,
+                   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 custom-plan 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 custom-plan 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 *
+CreateCustomPlanState(CustomPlan *custom_plan);
+</programlisting>
+   It populates a <literal>CustomPlanState</> (or inherited data type)
+   node according to the supplied <literal>CustomPlan</> node preliminary
+   constructed, on the beginning of query executor.
+   Only custom plan provider can know exact size of the node to be
+   allocated, this callback allocate a <literal>CustomPlanState</> node
+   with <literal>CustomExecMethods</> callbacks table and arbitrary
+   private fields.
+  </para>
+  <para>
+   Note that main purpose of this callback is allocation of
+   <literal>CustomPlanState</> node, not initialization of individual
+   fields because it shall be handled on the <literal>BeginCustomPlan</>
+   callback to be invoked next to the common usual initialization.
+  </para>
+
+  <para>
+<programlisting>
+void
+TextOutCustomPlan(StringInfo str,
+                  const CustomPlan *node);
+</programlisting>
+   It makes a text representation of custom plan node. If custom-plan
+   provider extends <literal>CustomPlan</> data type, it shall put private
+   fields on the supplied <literal>StringInfo</> with text form.
+   Note that common fields in <literal>CustomPlan</> are handled by backend,
+   so extension needs to do nothing special.
+  </para>
+
+  <para>
+<programlisting>
+CustomPlan *
+CopyCustomPlan(const CustomPlan *from);
+</programlisting>
+   It duplicate a <literal>CustomPlan</> node onto a newly allocated one.
+   If custom-plan provider extends the <literal>CustomPlan</> node, it shall
+   copy the private fields and callback table but no need to copy the common
+   <literal>scan</> field in the <literal>CustomPlan</> data type.
+  </para>
+ </sect1>
+
+ <sect1 id="cpp-exec-callbacks">
+  <title>Custom Executor Callbacks</title>
+  <para>
+   This section introduces callback functions of <literal>CustomPlanState</>
+   structure; defined in the <literal>CustomExecMethods</>.
+  </para>
+  <para>
+<programlisting>
+void
+BeginCustomPlan(CustomPlanState *node,
+                EState *estate,
+                int eflags);
+</programlisting>
+   It begins execution of custom-plan. This callback is invoked during
+   executor startup to initialize the supplied <literal>CustomPlanState</>
+   that was constructed on the <literal>CreateCustomPlanState</> above.
+   The custom-plan provider shall have initialization of its private fields
+   and common fields within <literal>CustomPlanState</> if needed, because
+   backend code already initializes expressions on its <literal>targetlist</>
+   and <literal>qual</>, assigns result slot according to the
+   <literal>targetlist</> and also assigns scan slot if <literal>scanrelid</>
+   is valid.
+  </para>
+
+  <para>
+<programlisting>
+TupleTableSlot *
+ExecCustomPlan(CustomPlanState *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
+   <literal>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 <literal>EState</>, to acquire per-scan
+   duration memory.
+  </para>
+
+  <para>
+<programlisting>
+Node *
+MultiExecCustomPlan(CustomPlanState *node);
+</programlisting>
+   It allows to return arbitrary data structure to the upper node, unlike
+   usual <literal>ExecCustomPlan</>. Built-in code has no invocation path
+   to call <literal>MultiExecProcNode</> towards <literal>CustomPlanState</>
+   node, so it is invoked only when a particular custom-plan provider made
+   a stacked custom-plan nodes and called <literal>MultiExecProcNode</> to
+   this underlying node.
+  </para>
+  <para>
+   Note that it is custom-plan provider's responsibility to translate the
+   arbitrary data structure into <productname>PostgreSQL</>'s complianced
+   data structure when top-level <literal>CustomPlanState</> returns a row
+   using <literal>ExecCustomPlan</>.
+  </para>
+
+  <para>
+<programlisting>
+void
+EndCustomPlan(CustomPlanState *node);
+</programlisting>
+   It ends the execution of custom plan and release any resources held by
+   this node. If custom-plan provider acquired resources that is not
+   released automatically at end of executor, it is responsibility of the
+   custom plan provider.
+  </para>
+
+  <para>
+<programlisting>
+void
+ReScanCustomPlan(CustomPlanState *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 *
+MarkPosCustomPlan(CustomPlanState *node);
+</programlisting>
+   It is an optional callback, if <literal>CustomPlanState</> was populated
+   from the <literal>CustomPlanMarkPos</>. Elsewhere, it shall not be called
+   and <literal>NULL</> is suitable.
+   It saves current scan position on somewhere in private fields of
+   <literal>CustomPlanState</>, to restore the position later.
+  </para>
+
+  <para>
+<programlisting>
+void *
+RestrPosCustomPlan(CustomPlanState *node);
+</programlisting>
+   It is an optional callback, if <literal>CustomPlanState</> was populated
+   from the <literal>CustomPlanMarkPos</>. Elsewhere, it shall not be called
+   and <literal>NULL</> is suitable.
+   It restores the previous scan position saved by
+   the <literal>MarkPosCustomPlan</> above.
+  </para>
+
+  <para>
+<programlisting>
+void
+ExplainCustomPlanTargetRel(CustomPlanState *node,
+                           ExplainState *es);
+</programlisting>
+   It is an optional callback, to show the target relation to be scanned.
+   In most cases, custom plan provider put text representation of the relation
+   to be scanned according to the manner in <literal>ExplainTargetRel</>.
+  </para>
+
+  <para>
+<programlisting>
+void
+ExplainCustomPlan(CustomPlanState *node,
+                  List *ancestors,
+                  ExplainState *es);
+</programlisting>
+   It is an optional callback, to show custom-plan specific explain output.
+  </para>
+
+  <para>
+<programlisting>
+void
+ExplainCustomPreScanNode(CustomPlanState *node,
+                         Bitmapset **rels_used);
+</programlisting>
+   It is an optional callback, to inform the backend which relation is
+   referenced. It shall set <literal>scanrelid</> of the target relation.
+   If <literal>NULL</>, it means this custom-plan provider never
+   references base relations.
+  </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>CustomPlanState</> when <literal>EXPLAIN</> needs the
+   text form of the column actually referenced.
+   In case when custom-plan provider adjusted <literal>varno</> of varnodes
+   on the expression tree to use special varnos (<literal>INNER_VAR</>,
+   <literal>OUTER_VAR</> or <literal>INDEX_VAR</>), custom-plan 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 <literal>PlanState</> that shall be
+   set on the <literal>child_ps</> argument.
+  </para>
+ </sect1>
+</chapter>
+
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 5902f97..a9b9efc 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-provider SYSTEM "custom-plan-provider.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..4702178 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -242,6 +242,7 @@
   &nls;
   &plhandler;
   &fdwhandler;
+  &custom-plan-provider;
   &geqo;
   &indexam;
   &gist;
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index b685e16..ab35742 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -55,6 +55,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY createCast         SYSTEM "create_cast.sgml">
 <!ENTITY createCollation    SYSTEM "create_collation.sgml">
 <!ENTITY createConversion   SYSTEM "create_conversion.sgml">
+<!ENTITY createCustomPlanProvider SYSTEM "create_custom_plan_provider.sgml">
 <!ENTITY createDatabase     SYSTEM "create_database.sgml">
 <!ENTITY createDomain       SYSTEM "create_domain.sgml">
 <!ENTITY createEventTrigger SYSTEM "create_event_trigger.sgml">
@@ -95,6 +96,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY dropCast           SYSTEM "drop_cast.sgml">
 <!ENTITY dropCollation      SYSTEM "drop_collation.sgml">
 <!ENTITY dropConversion     SYSTEM "drop_conversion.sgml">
+<!ENTITY dropCustomPlanProvider SYSTEM "drop_custom_plan_provider.sgml">
 <!ENTITY dropDatabase       SYSTEM "drop_database.sgml">
 <!ENTITY dropDomain         SYSTEM "drop_domain.sgml">
 <!ENTITY dropEventTrigger   SYSTEM "drop_event_trigger.sgml">
diff --git a/doc/src/sgml/ref/create_custom_plan_provider.sgml b/doc/src/sgml/ref/create_custom_plan_provider.sgml
new file mode 100644
index 0000000..0816584
--- /dev/null
+++ b/doc/src/sgml/ref/create_custom_plan_provider.sgml
@@ -0,0 +1,139 @@
+<!--
+doc/src/sgml/ref/create_custom_plan_provider.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-CREATECUSTOMPLANPROVIDER">
+ <indexterm zone="sql-createcustomplanprovider">
+  <primary>CREATE CUSTOM PLAN PROVIDER</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>CREATE CUSTOM PLAN PROVIDER</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>CREATE CUSTOM PLAN PROVIDER</refname>
+  <refpurpose>define a new custom plan provider</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+CREATE CUSTOM PLAN PROVIDER <replaceable class="parameter">cpp_name</replaceable> FOR <replaceable class="parameter">cpp_class</replaceable>
+    HANDLER <replaceable class="parameter">handler_function</replaceable>
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>CREATE CUSTOM PLAN PROVIDER</command> defines a new custom-plan
+   provider.
+   The user who defines the custom-plan provider has to be a superuser.
+  </para>
+
+  <para>
+   A custom-plan provider can offer the query planner alternative options
+   to scan relation, or potentially join relations and so on, in addition
+   to the built-in logics. It is usually extension modules that implement
+   callbacks according to the custom-plan interface.
+  </para>
+  <para>
+   This statement defines a couple of an entrypoint of custom-plan provider
+   and its supporting workload type. Right now, <literal>scan</literal> is
+   the only class being supported; that enables to call extension's
+   callback during query execution instead of built-in routines like
+   <literal>SeqScan</literal> or <literal>IndexScan</literal> if its
+   cost estimation is enough reasonable.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">cpp_name</replaceable></term>
+    <listitem>
+     <para>
+      The name of the custom-plan provider to be created.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">cpp_class</replaceable></term>
+    <listitem>
+     <para>
+      Workload type on which custom-plan provider can perform.
+      Only <literal>SCAN</literal> is supported option right now.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">handler_function</replaceable></term>
+    <listitem>
+     <para>
+      A function to be called when query planner is finding the best path
+      to scan a relation.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+   The function that performs as a custom-plan provider shall be declared
+   to return <literal>void</> and take one argument with <literal>internal</>
+   data type.
+   The core <productname>PostgreSQL</> calls custom-plan provider function
+   with a set of information about planner's state and relation(s) to be
+   scanned, then this function shall check whether it can offer alternative
+   scan paths or not.
+   If available, it constructs a path object derived from
+   <literal>CustomPath</> structure, that contains a set of callbacks
+   including the ones to populate <literal>CustomPlan</> or
+   <literal>CustomPlanState</> object later.
+   If extension needs to save its private information in these object,
+   define a new structure that extends above data types.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+  <para>
+   Create a custom-plan provider <literal>ctidscan</> that uses the funcion
+   <literal>ctidscanaddpath</>.   
+<programlisting>
+CREATE CUSTOM PLAN PROVIDER ctidscan FOR scan HANDLER ctidscanaddpath;
+</programlisting>
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+  <para>
+   There is no <command>CREATE CUSTOM PLAN</command> command
+   in the SQL standard.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-dropcustomplanprovider"></member>
+  </simplelist>
+  <simplelist type="inline">
+   <member><xref linkend="custom-plan"></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/drop_custom_plan_provider.sgml b/doc/src/sgml/ref/drop_custom_plan_provider.sgml
new file mode 100644
index 0000000..6a305a8
--- /dev/null
+++ b/doc/src/sgml/ref/drop_custom_plan_provider.sgml
@@ -0,0 +1,108 @@
+<!--
+doc/src/sgml/ref/drop_custom_plan_provider.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-DROPCUSTOMPLANPROVIDER">
+ <indexterm zone="sql-dropcustomplanprovider">
+  <primary>DROP CUSTOM PLAN PROVIDER</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>DROP CUSTOM PLAN PROVIDER</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>DROP CUSTOM PLAN PROVIDER</refname>
+  <refpurpose>remove a custom-plan provider</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+DROP CUSTOM PLAN PROVIDER [ IF EXISTS ] <replaceable class="parameter">cpp_name</replaceable> [ CASCADE | RESTRICT ]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>DROP CUSTOM PLAN PROVIDER</command> removes an existing custom
+   plan provider. To execute this command, the current user must be superuser.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>IF EXISTS</literal></term>
+    <listitem>
+     <para>
+      Do not throw an error if the custom-plan provider does not exist.
+      A notice is issued in this case.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name of an existing custom-plan provider.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>CASCADE</literal></term>
+    <listitem>
+     <para>
+      Automatically drop objects that depend on the custom-plan provider.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>RESTRICT</literal></term>
+    <listitem>
+     <para>
+      Refuse to drop the custom-plan provider if any objects depend on it.
+      This is the default.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   Drop a custom-plan provider <literal>foo</> if it exists:
+<programlisting>
+DROP CUSTOM PLAN PROVIDER IF EXISTS foo;
+</programlisting></para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   There is no <command>DROP CUSTOM PLAN</command> command
+   in the SQL standard.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-createcustomplanprovider"></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index 6ec1263..1a3dbdd 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -83,6 +83,7 @@
    &createCast;
    &createCollation;
    &createConversion;
+   &createCustomPlanProvider;
    &createDatabase;
    &createDomain;
    &createEventTrigger;
@@ -123,6 +124,7 @@
    &dropCast;
    &dropCollation;
    &dropConversion;
+   &dropCustomPlanProvider;
    &dropDatabase;
    &dropDomain;
    &dropEventTrigger;
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index a974bd5..f7e29eb 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -39,7 +39,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
 	pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
 	pg_ts_parser.h pg_ts_template.h pg_extension.h \
 	pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
-	pg_foreign_table.h \
+	pg_foreign_table.h pg_custom_plan_provider.h \
 	pg_default_acl.h pg_seclabel.h pg_shseclabel.h pg_collation.h pg_range.h \
 	toasting.h indexing.h \
     )
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index d41ba49..496bc9a 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -30,6 +30,7 @@
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_conversion.h"
 #include "catalog/pg_conversion_fn.h"
+#include "catalog/pg_custom_plan_provider.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_default_acl.h"
 #include "catalog/pg_depend.h"
@@ -154,7 +155,8 @@ static const Oid object_classes[MAX_OCLASS] = {
 	UserMappingRelationId,		/* OCLASS_USER_MAPPING */
 	DefaultAclRelationId,		/* OCLASS_DEFACL */
 	ExtensionRelationId,		/* OCLASS_EXTENSION */
-	EventTriggerRelationId		/* OCLASS_EVENT_TRIGGER */
+	EventTriggerRelationId,		/* OCLASS_EVENT_TRIGGER */
+	CustomPlanProviderRelationId,		/* OCLASS_CPP */
 };
 
 
@@ -1249,6 +1251,10 @@ doDeletion(const ObjectAddress *object, int flags)
 			RemoveEventTriggerById(object->objectId);
 			break;
 
+		case OCLASS_CPP:
+			RemoveCustomPlanProviderById(object->objectId);
+			break;
+
 		default:
 			elog(ERROR, "unrecognized object class: %u",
 				 object->classId);
@@ -2316,6 +2322,9 @@ getObjectClass(const ObjectAddress *object)
 
 		case EventTriggerRelationId:
 			return OCLASS_EVENT_TRIGGER;
+
+		case CustomPlanProviderRelationId:
+			return OCLASS_CPP;
 	}
 
 	/* shouldn't get here */
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index c7c8f4b..2b0b0e1 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -30,6 +30,7 @@
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_conversion.h"
+#include "catalog/pg_custom_plan_provider.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_extension.h"
 #include "catalog/pg_foreign_data_wrapper.h"
@@ -152,6 +153,18 @@ static const ObjectPropertyType ObjectProperty[] =
 		true
 	},
 	{
+		CustomPlanProviderRelationId,
+		CustomPlanProviderOidIndexId,
+		CUSTOMPLANPROVIDEROID,
+		CUSTOMPLANPROVIDERNAME,
+		Anum_pg_custom_plan_provider_cppname,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		-1,
+		false,
+	},
+	{
 		DatabaseRelationId,
 		DatabaseOidIndexId,
 		DATABASEOID,
@@ -529,6 +542,7 @@ get_object_address(ObjectType objtype, List *objname, List *objargs,
 			case OBJECT_FDW:
 			case OBJECT_FOREIGN_SERVER:
 			case OBJECT_EVENT_TRIGGER:
+			case OBJECT_CPP:
 				address = get_object_address_unqualified(objtype,
 														 objname, missing_ok);
 				break;
@@ -755,6 +769,9 @@ get_object_address_unqualified(ObjectType objtype,
 			case OBJECT_EVENT_TRIGGER:
 				msg = gettext_noop("event trigger name cannot be qualified");
 				break;
+			case OBJECT_CPP:
+				msg = gettext_noop("custom plan provider name cannot be qualified");
+				break;
 			default:
 				elog(ERROR, "unrecognized objtype: %d", (int) objtype);
 				msg = NULL;		/* placate compiler */
@@ -815,6 +832,11 @@ get_object_address_unqualified(ObjectType objtype,
 			address.objectId = get_event_trigger_oid(name, missing_ok);
 			address.objectSubId = 0;
 			break;
+		case OBJECT_CPP:
+			address.classId = CustomPlanProviderRelationId;
+			address.objectId = get_custom_plan_provider_oid(name, missing_ok);
+			address.objectSubId = 0;
+			break;
 		default:
 			elog(ERROR, "unrecognized objtype: %d", (int) objtype);
 			/* placate compiler, which doesn't know elog won't return */
@@ -1295,6 +1317,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
 			break;
 		case OBJECT_TSPARSER:
 		case OBJECT_TSTEMPLATE:
+		case OBJECT_CPP:
 			/* We treat these object types as being owned by superusers */
 			if (!superuser_arg(roleid))
 				ereport(ERROR,
@@ -2166,6 +2189,24 @@ getObjectDescription(const ObjectAddress *object)
 				break;
 			}
 
+		case OCLASS_CPP:
+			{
+				Form_pg_custom_plan_provider cpp_form;
+				HeapTuple	tup;
+
+				tup = SearchSysCache1(CUSTOMPLANPROVIDEROID,
+									  ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR,
+						 "cache lookup failed for custom-plan provider %u",
+						 object->objectId);
+				cpp_form = (Form_pg_custom_plan_provider) GETSTRUCT(tup);
+				appendStringInfo(&buffer, _("custom plan provider %s"),
+								 NameStr(cpp_form->cppname));
+				ReleaseSysCache(tup);
+				break;
+			}
+
 		default:
 			appendStringInfo(&buffer, "unrecognized object %u %u %d",
 							 object->classId,
@@ -2577,6 +2618,10 @@ getObjectTypeDescription(const ObjectAddress *object)
 			appendStringInfoString(&buffer, "event trigger");
 			break;
 
+		case OCLASS_CPP:
+			appendStringInfoString(&buffer, "custom plan provider");
+			break;
+
 		default:
 			appendStringInfo(&buffer, "unrecognized %u", object->classId);
 			break;
@@ -3330,6 +3375,24 @@ getObjectIdentity(const ObjectAddress *object)
 				break;
 			}
 
+		case OCLASS_CPP:
+			{
+				HeapTuple	tup;
+				Form_pg_custom_plan_provider cpp_form;
+
+				tup = SearchSysCache1(CUSTOMPLANPROVIDEROID,
+									  ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR,
+						 "cache lookup failed for custom-plan provider %u",
+						 object->objectId);
+				cpp_form = (Form_pg_custom_plan_provider) GETSTRUCT(tup);
+				appendStringInfoString(&buffer,
+						 quote_identifier(NameStr(cpp_form->cppname)));
+				ReleaseSysCache(tup);
+				break;
+			}
+
 		default:
 			appendStringInfo(&buffer, "unrecognized object %u %u %d",
 							 object->classId,
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index 22f116b..1e8e6f4 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -14,7 +14,7 @@ include $(top_builddir)/src/Makefile.global
 
 OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o  \
 	collationcmds.o constraint.o conversioncmds.o copy.o createas.o \
-	dbcommands.o define.o discard.o dropcmds.o \
+	custom_plan.o dbcommands.o define.o discard.o dropcmds.o \
 	event_trigger.o explain.o extension.o foreigncmds.o functioncmds.o \
 	indexcmds.o lockcmds.o matview.o operatorcmds.o opclasscmds.o \
 	portalcmds.o prepare.o proclang.o \
diff --git a/src/backend/commands/custom_plan.c b/src/backend/commands/custom_plan.c
new file mode 100644
index 0000000..2a81a7b
--- /dev/null
+++ b/src/backend/commands/custom_plan.c
@@ -0,0 +1,188 @@
+/*-------------------------------------------------------------------------
+ *
+ * custom_plan.c
+ *		custom plan nodes creation/manipulation commands
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ *    src/backend/commands/custom_plan.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_custom_plan_provider.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+#include "commands/defrem.h"
+#include "fmgr.h"
+#include "miscadmin.h"
+#include "parser/parse_func.h"
+#include "utils/builtins.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+/*
+ * utility function to lookup a custom-plan provider by name
+ */
+Oid
+get_custom_plan_provider_oid(const char *cpp_name, bool missing_ok)
+{
+	Oid		cpp_oid;
+
+	cpp_oid = GetSysCacheOid1(CUSTOMPLANPROVIDERNAME,
+							   CStringGetDatum(cpp_name));
+	if (!OidIsValid(cpp_oid) && !missing_ok)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("custom-plan provider \"%s\" does not exist",
+						cpp_name)));
+	return cpp_oid;
+}
+
+/*
+ * Drop a custom-plan provider
+ */
+void
+RemoveCustomPlanProviderById(Oid cpp_oid)
+{
+	Relation	rel;
+	HeapTuple	tuple;
+
+	rel = heap_open(CustomPlanProviderRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCache1(CUSTOMPLANPROVIDEROID,
+							ObjectIdGetDatum(cpp_oid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for custom-plan provider %u",
+			 cpp_oid);
+
+	simple_heap_delete(rel, &tuple->t_self);
+
+	ReleaseSysCache(tuple);
+
+	heap_close(rel, RowExclusiveLock);
+}
+
+/*
+ * Create a custom-plan provider
+ */
+Oid
+DefineCustomPlanProvider(CreateCustomPlanProviderStmt *stmt)
+{
+	Relation	rel;
+	Oid			cpp_oid;
+	Oid			cpp_handler = InvalidOid;
+	Datum		values[Natts_pg_custom_plan_provider];
+	bool		isnull[Natts_pg_custom_plan_provider];
+	HeapTuple	tuple;
+	ListCell   *cell;
+	ObjectAddress myself;
+	ObjectAddress referenced;
+
+	rel = heap_open(CustomPlanProviderRelationId, RowExclusiveLock);
+
+	/* must be super user */
+	if (!superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+			 errmsg("permission denied to create custom-plan provider \"%s\"",
+					stmt->cpp_name),
+			 errhint("Must be superuser to create a custom-plan node.")));
+
+	/* check namespace conflicts */
+	cpp_oid = get_custom_plan_provider_oid(stmt->cpp_name, true);
+	if (OidIsValid(cpp_oid))
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("custom-plan provider \"%s\" already exists",
+						stmt->cpp_name)));
+
+	/* check custom-plan class */
+	if (stmt->cpp_class != CUSTOMPLAN_CLASS_SCAN)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("unexpected custom plan class specified: %d",
+						(int)stmt->cpp_class)));
+
+	/* parse custom-plan options */
+	foreach (cell, stmt->cpp_options)
+	{
+		DefElem	   *defel = lfirst(cell);
+
+		Assert(IsA(defel, DefElem));
+
+		if (strcmp(defel->defname, "handler") == 0)
+		{
+			Oid		argtypes[1];
+
+			if (OidIsValid(cpp_handler))
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+
+			argtypes[0] = INTERNALOID;
+			cpp_handler = LookupFuncName((List *)defel->arg,
+										 1, argtypes, false);
+			if (get_func_rettype(cpp_handler) != VOIDOID)
+				ereport(ERROR,
+						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+						 errmsg("function %s must return type \"void\"",
+								NameListToString((List *) defel->arg))));
+		}
+		else
+			elog(ERROR, "unexpected custom-plan provider option: %s",
+				 defel->defname);
+	}
+
+	if (!OidIsValid(cpp_handler))
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("HANDLER must be provided")));
+
+	/*
+	 * Insert tuple into pg_custom_plan system catalog
+	 */
+	memset(values, 0, sizeof(values));
+	memset(isnull, 0, sizeof(isnull));
+	values[Anum_pg_custom_plan_provider_cppname - 1]
+		= DirectFunctionCall1(namein, CStringGetDatum(stmt->cpp_name));
+	values[Anum_pg_custom_plan_provider_cppclass - 1]
+		= stmt->cpp_class;
+	values[Anum_pg_custom_plan_provider_cpphandler - 1]
+		= ObjectIdGetDatum(cpp_handler);
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), values, isnull);
+
+	cpp_oid = simple_heap_insert(rel, tuple);
+	CatalogUpdateIndexes(rel, tuple);
+
+	heap_freetuple(tuple);
+
+	/* record dependencies */
+	myself.classId = CustomPlanProviderRelationId;
+	myself.objectId = cpp_oid;
+	myself.objectSubId = 0;
+
+	referenced.classId = ProcedureRelationId;
+	referenced.objectId = cpp_handler;
+	referenced.objectSubId = 0;
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+	/* Post creation hook for new custom-plan provider */
+	InvokeObjectPostCreateHook(CustomPlanProviderRelationId, cpp_oid, 0);
+
+	heap_close(rel, RowExclusiveLock);
+
+	return cpp_oid;
+}
diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c
index e64ad80..c6d4576 100644
--- a/src/backend/commands/dropcmds.c
+++ b/src/backend/commands/dropcmds.c
@@ -408,6 +408,11 @@ does_not_exist_skipping(ObjectType objtype, List *objname, List *objargs)
 				args = strVal(linitial(objargs));
 			}
 			break;
+		case OBJECT_CPP:
+			msg = gettext_noop("custom-plan provider \"%s\" does not exist, skipping");
+			name = NameListToString(objname);
+			break;
+
 		default:
 			elog(ERROR, "unexpected object type (%d)", (int) objtype);
 			break;
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 754264e..76968a2 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -923,6 +923,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
 		case OBJECT_CONSTRAINT:
 		case OBJECT_COLLATION:
 		case OBJECT_CONVERSION:
+		case OBJECT_CPP:
 		case OBJECT_DOMAIN:
 		case OBJECT_EXTENSION:
 		case OBJECT_FDW:
@@ -975,6 +976,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
 		case OCLASS_COLLATION:
 		case OCLASS_CONSTRAINT:
 		case OCLASS_CONVERSION:
+		case OCLASS_CPP:
 		case OCLASS_DEFAULT:
 		case OCLASS_LANGUAGE:
 		case OCLASS_LARGEOBJECT:
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 781a736..7ae2160 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -722,6 +722,15 @@ ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
 			*rels_used = bms_add_member(*rels_used,
 										((Scan *) plan)->scanrelid);
 			break;
+		case T_CustomPlan:
+		case T_CustomPlanMarkPos:
+			{
+				CustomPlanState *cps = (CustomPlanState *)planstate;
+
+				if (cps->methods->ExplainCustomPreScanNode)
+					cps->methods->ExplainCustomPreScanNode(cps, rels_used);
+			}
+			break;
 		case T_ModifyTable:
 			/* cf ExplainModifyTarget */
 			*rels_used = bms_add_member(*rels_used,
@@ -848,6 +857,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 +946,14 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_ForeignScan:
 			pname = sname = "Foreign Scan";
 			break;
+		case T_CustomPlan:
+			sname = "Custom";
+			custom_name = ((CustomPlan *) plan)->methods->CustomName;
+			if (custom_name)
+				pname = psprintf("Custom (%s)", custom_name);
+			else
+				pname = sname;
+			break;
 		case T_Material:
 			pname = sname = "Materialize";
 			break;
@@ -1037,6 +1055,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", custom_name, es);
 	}
 
 	switch (nodeTag(plan))
@@ -1084,6 +1104,14 @@ ExplainNode(PlanState *planstate, List *ancestors,
 					ExplainPropertyText("Index Name", indexname, es);
 			}
 			break;
+		case T_CustomPlan:
+			{
+				CustomPlanState *cps = (CustomPlanState *)planstate;
+
+				if (cps->methods->ExplainCustomPlanTargetRel)
+					cps->methods->ExplainCustomPlanTargetRel(cps, es);
+			}
+			break;
 		case T_ModifyTable:
 			ExplainModifyTarget((ModifyTable *) plan, es);
 			break;
@@ -1353,6 +1381,18 @@ ExplainNode(PlanState *planstate, List *ancestors,
 										   planstate, es);
 			show_foreignscan_info((ForeignScanState *) planstate, es);
 			break;
+		case T_CustomPlan:
+			{
+				CustomPlanState *cps = (CustomPlanState *) planstate;
+
+				show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
+				if (plan->qual)
+					show_instrumentation_count("Rows Removed by Filter", 1,
+											   planstate, es);
+				if (cps->methods->ExplainCustomPlan)
+					cps->methods->ExplainCustomPlan(cps, 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..800c969 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -21,6 +21,7 @@
 #include "executor/nodeBitmapIndexscan.h"
 #include "executor/nodeBitmapOr.h"
 #include "executor/nodeCtescan.h"
+#include "executor/nodeCustom.h"
 #include "executor/nodeForeignscan.h"
 #include "executor/nodeFunctionscan.h"
 #include "executor/nodeGroup.h"
@@ -197,6 +198,10 @@ ExecReScan(PlanState *node)
 			ExecReScanForeignScan((ForeignScanState *) node);
 			break;
 
+		case T_CustomPlanState:
+			ExecReScanCustomPlan((CustomPlanState *) node);
+			break;
+
 		case T_NestLoopState:
 			ExecReScanNestLoop((NestLoopState *) node);
 			break;
@@ -291,6 +296,10 @@ ExecMarkPos(PlanState *node)
 			ExecValuesMarkPos((ValuesScanState *) node);
 			break;
 
+		case T_CustomPlanState:
+			ExecCustomMarkPos((CustomPlanState *) node);
+			break;
+
 		case T_MaterialState:
 			ExecMaterialMarkPos((MaterialState *) node);
 			break;
@@ -348,6 +357,10 @@ ExecRestrPos(PlanState *node)
 			ExecValuesRestrPos((ValuesScanState *) node);
 			break;
 
+		case T_CustomPlanState:
+			ExecCustomRestrPos((CustomPlanState *) node);
+			break;
+
 		case T_MaterialState:
 			ExecMaterialRestrPos((MaterialState *) node);
 			break;
@@ -390,6 +403,7 @@ ExecSupportsMarkRestore(NodeTag plantype)
 		case T_ValuesScan:
 		case T_Material:
 		case T_Sort:
+		case T_CustomPlanMarkPos:
 			return true;
 
 		case T_Result:
@@ -465,6 +479,17 @@ ExecSupportsBackwardScan(Plan *node)
 			return ExecSupportsBackwardScan(((SubqueryScan *) node)->subplan) &&
 				TargetListSupportsBackwardScan(node->targetlist);
 
+		case T_CustomPlan:
+			{
+				CustomPlan *cplan = (CustomPlan *) node;
+
+				if (!TargetListSupportsBackwardScan(node->targetlist))
+					return false;
+				if (cplan->methods->SupportBackwardScan)
+					return cplan->methods->SupportBackwardScan(cplan);
+			}
+			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..62ebab9 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,12 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
 													   estate, eflags);
 			break;
 
+		case T_CustomPlan:
+		case T_CustomPlanMarkPos:
+			result = (PlanState *) ExecInitCustomPlan((CustomPlan *) node,
+													  estate, eflags);
+			break;
+
 			/*
 			 * join nodes
 			 */
@@ -442,6 +449,10 @@ ExecProcNode(PlanState *node)
 			result = ExecForeignScan((ForeignScanState *) node);
 			break;
 
+		case T_CustomPlanState:
+			result = ExecCustomPlan((CustomPlanState *) node);
+			break;
+
 			/*
 			 * join nodes
 			 */
@@ -558,6 +569,10 @@ MultiExecProcNode(PlanState *node)
 			result = MultiExecBitmapOr((BitmapOrState *) node);
 			break;
 
+		case T_CustomPlanState:
+			result = MultiExecCustomPlan((CustomPlanState *) node);
+			break;
+
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
 			result = NULL;
@@ -678,6 +693,10 @@ ExecEndNode(PlanState *node)
 			ExecEndForeignScan((ForeignScanState *) node);
 			break;
 
+		case T_CustomPlanState:
+			ExecEndCustomPlan((CustomPlanState *) node);
+			break;
+
 			/*
 			 * join nodes
 			 */
diff --git a/src/backend/executor/nodeCustom.c b/src/backend/executor/nodeCustom.c
new file mode 100644
index 0000000..785909e
--- /dev/null
+++ b/src/backend/executor/nodeCustom.c
@@ -0,0 +1,147 @@
+/* ------------------------------------------------------------------------
+ *
+ * nodeCustom.c
+ *		Routines to handle execution of custom plan 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"
+
+CustomPlanState *
+ExecInitCustomPlan(CustomPlan *cplan, EState *estate, int eflags)
+{
+	CustomPlanState    *cps;
+
+	/* populate a CustomPlanState according to the CustomPlan */
+	cps = (CustomPlanState *)cplan->methods->CreateCustomPlanState(cplan);
+	Assert(IsA(cps, CustomPlanState));
+
+	/* fill up fields of PlanState */
+	cps->ss.ps.plan = &cplan->scan.plan;
+	cps->ss.ps.state = estate;
+
+	/* create expression context for node */
+	ExecAssignExprContext(estate, &cps->ss.ps);
+	cps->ss.ps.ps_TupFromTlist = false;
+
+	/* initialize child expressions */
+	cps->ss.ps.targetlist = (List *)
+		ExecInitExpr((Expr *) cplan->scan.plan.targetlist,
+					 (PlanState *) cps);
+	cps->ss.ps.qual = (List *)
+		ExecInitExpr((Expr *) cplan->scan.plan.qual,
+					 (PlanState *) cps);
+
+	/* initialization of result tuple slot  */
+	ExecInitResultTupleSlot(estate, &cps->ss.ps);
+	ExecAssignResultTypeFromTL(&cps->ss.ps);
+
+	if (cplan->scan.scanrelid > 0)
+	{
+		Relation	heap_rel;
+
+		heap_rel = ExecOpenScanRelation(estate, cplan->scan.scanrelid, eflags);
+		cps->ss.ss_currentRelation = heap_rel;
+		cps->ss.ss_currentScanDesc = NULL;	/* set by provider on demand */
+		ExecInitScanTupleSlot(estate, &cps->ss);
+		ExecAssignScanType(&cps->ss, RelationGetDescr(heap_rel));
+		ExecAssignScanProjectionInfo(&cps->ss);
+	}
+	else
+	{
+		/*
+		 * Elsewhere, custom-plan provider should be responsible to put
+		 * appropriate initialization of scan tuple-slot and projection
+		 * info.
+		 */
+		cps->ss.ss_currentRelation = NULL;
+		cps->ss.ss_currentScanDesc = NULL;
+		cps->ss.ss_ScanTupleSlot = NULL;
+		cps->ss.ps.ps_ProjInfo = NULL;
+	}
+	/*
+	 * Then, custom-plan provider can have all the own original
+	 * initialization on demand.
+	 */
+	cps->methods->BeginCustomPlan(cps, estate, eflags);
+
+	return cps;
+}
+
+TupleTableSlot *
+ExecCustomPlan(CustomPlanState *cpstate)
+{
+	Assert(cpstate->methods->ExecCustomPlan != NULL);
+	return cpstate->methods->ExecCustomPlan(cpstate);
+}
+
+Node *
+MultiExecCustomPlan(CustomPlanState *cpstate)
+{
+	if (!cpstate->methods->MultiExecCustomPlan)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("CustomPlan \"%s\" does not support MultiExec method",
+						cpstate->methods->CustomName)));
+	return cpstate->methods->MultiExecCustomPlan(cpstate);
+}
+
+void
+ExecEndCustomPlan(CustomPlanState *cpstate)
+{
+	Assert(cpstate->methods->EndCustomPlan != NULL);
+	cpstate->methods->EndCustomPlan(cpstate);
+
+	/* Free the exprcontext */
+	ExecFreeExprContext(&cpstate->ss.ps);
+
+	/* Clean out the tuple table */
+	ExecClearTuple(cpstate->ss.ps.ps_ResultTupleSlot);
+	if (cpstate->ss.ss_ScanTupleSlot)
+		ExecClearTuple(cpstate->ss.ss_ScanTupleSlot);
+
+	/* Close the heap relation, if needed */
+	if (cpstate->ss.ss_currentRelation)
+		ExecCloseScanRelation(cpstate->ss.ss_currentRelation);
+}
+
+void
+ExecReScanCustomPlan(CustomPlanState *cpstate)
+{
+	Assert(cpstate->methods->ReScanCustomPlan != NULL);
+	cpstate->methods->ReScanCustomPlan(cpstate);
+}
+
+void
+ExecCustomMarkPos(CustomPlanState *cpstate)
+{
+	if (!cpstate->methods->MarkPosCustomPlan)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+		errmsg("MarkPos is not supported by custom plan provider: %s",
+			   cpstate->methods->CustomName)));
+	cpstate->methods->MarkPosCustomPlan(cpstate);
+}
+
+void
+ExecCustomRestrPos(CustomPlanState *cpstate)
+{
+	if (!cpstate->methods->RestrPosCustomPlan)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+		errmsg("RestrPos is not supported by custom plan provider: %s",
+			   cpstate->methods->CustomName)));
+	cpstate->methods->RestrPosCustomPlan(cpstate);
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 3088578..b78c6ec 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -598,6 +598,21 @@ _copyForeignScan(const ForeignScan *from)
 }
 
 /*
+ * _copyCustomPlan
+ */
+static CustomPlan *
+_copyCustomPlan(const CustomPlan *from)
+{
+	CustomPlan *newnode;
+
+	newnode = from->methods->CopyCustomPlan(from);
+	Assert(nodeTag(newnode) == nodeTag(from));
+	CopyScanFields((const Scan *) from, (Scan *) newnode);
+
+	return newnode;
+}
+
+/*
  * CopyJoinFields
  *
  *		This function copies the fields of the Join node.  It is used by
@@ -3849,6 +3864,19 @@ _copyAlterTSConfigurationStmt(const AlterTSConfigurationStmt *from)
 	return newnode;
 }
 
+static CreateCustomPlanProviderStmt *
+_copyCreateCustomPlanProviderStmt(const CreateCustomPlanProviderStmt *from)
+{
+	CreateCustomPlanProviderStmt *newnode
+		= makeNode(CreateCustomPlanProviderStmt);
+
+	COPY_STRING_FIELD(cpp_name);
+	COPY_SCALAR_FIELD(cpp_class);
+	COPY_NODE_FIELD(cpp_options);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					pg_list.h copy functions
  * ****************************************************************
@@ -4012,6 +4040,10 @@ copyObject(const void *from)
 		case T_ForeignScan:
 			retval = _copyForeignScan(from);
 			break;
+		case T_CustomPlan:
+		case T_CustomPlanMarkPos:
+			retval = _copyCustomPlan(from);
+			break;
 		case T_Join:
 			retval = _copyJoin(from);
 			break;
@@ -4561,6 +4593,9 @@ copyObject(const void *from)
 		case T_AlterTSConfigurationStmt:
 			retval = _copyAlterTSConfigurationStmt(from);
 			break;
+		case T_CreateCustomPlanProviderStmt:
+			retval = _copyCreateCustomPlanProviderStmt(from);
+			break;
 
 		case T_A_Expr:
 			retval = _copyAExpr(from);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 1b07db6..bdd626e 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2008,6 +2008,17 @@ _equalAlterTSConfigurationStmt(const AlterTSConfigurationStmt *a,
 }
 
 static bool
+_equalCreateCustomPlanProviderStmt(const CreateCustomPlanProviderStmt *a,
+								   const CreateCustomPlanProviderStmt *b)
+{
+	COMPARE_STRING_FIELD(cpp_name);
+	COMPARE_SCALAR_FIELD(cpp_class);
+	COMPARE_NODE_FIELD(cpp_options);
+
+	return true;
+}
+
+static bool
 _equalAExpr(const A_Expr *a, const A_Expr *b)
 {
 	COMPARE_SCALAR_FIELD(kind);
@@ -3025,6 +3036,9 @@ equal(const void *a, const void *b)
 		case T_AlterTSConfigurationStmt:
 			retval = _equalAlterTSConfigurationStmt(a, b);
 			break;
+		case T_CreateCustomPlanProviderStmt:
+			retval = _equalCreateCustomPlanProviderStmt(a, b);
+			break;
 
 		case T_A_Expr:
 			retval = _equalAExpr(a, b);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 9573a9b..e010705 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -564,6 +564,22 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
 }
 
 static void
+_outCustomPlan(StringInfo str, const CustomPlan *node)
+{
+	if (IsA(node, CustomPlan))
+		WRITE_NODE_TYPE("CUSTOMPLAN");
+	else if (IsA(node, CustomPlanMarkPos))
+		WRITE_NODE_TYPE("CUSTOMPLANMARKPOS");
+	else
+		elog(ERROR, "unexpected node tag given: %d", (int)nodeTag(node));
+
+	_outScanInfo(str, (const Scan *) node);
+	appendStringInfo(str, " :methods");
+	_outToken(str, node->methods->CustomName);
+	node->methods->TextOutCustomPlan(str, node);
+}
+
+static void
 _outJoin(StringInfo str, const Join *node)
 {
 	WRITE_NODE_TYPE("JOIN");
@@ -1582,6 +1598,16 @@ _outForeignPath(StringInfo str, const ForeignPath *node)
 }
 
 static void
+_outCustomPath(StringInfo str, const CustomPath *node)
+{
+	WRITE_NODE_TYPE("CUSTOMPATH");
+	_outPathInfo(str, (const Path *) node);
+	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");
@@ -2845,6 +2871,10 @@ _outNode(StringInfo str, const void *obj)
 			case T_ForeignScan:
 				_outForeignScan(str, obj);
 				break;
+			case T_CustomPlan:
+			case T_CustomPlanMarkPos:
+				_outCustomPlan(str, obj);
+				break;
 			case T_Join:
 				_outJoin(str, obj);
 				break;
@@ -3053,6 +3083,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..dbba5ae 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -336,7 +336,7 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 				}
 				break;
 			case RTE_SUBQUERY:
-				/* Subquery --- fully handled during set_rel_size */
+				/* Subquery --- path was added during set_rel_size */
 				break;
 			case RTE_FUNCTION:
 				/* RangeFunction */
@@ -347,12 +347,17 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 				set_values_pathlist(root, rel, rte);
 				break;
 			case RTE_CTE:
-				/* CTE reference --- fully handled during set_rel_size */
+				/* CTE reference --- path was added during set_rel_size */
 				break;
 			default:
 				elog(ERROR, "unexpected rtekind: %d", (int) rel->rtekind);
 				break;
 		}
+		/* Also, consider paths by custom-plan providers */
+		call_custom_scan_providers(root, rel, rte);
+
+		/* Select cheapest path */
+		set_cheapest(rel);
 	}
 
 #ifdef OPTIMIZER_DEBUG
@@ -401,9 +406,6 @@ set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 
 	/* Consider TID scans */
 	create_tidscan_paths(root, rel);
-
-	/* Now find the cheapest of the paths for this rel */
-	set_cheapest(rel);
 }
 
 /*
@@ -429,9 +431,6 @@ set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 {
 	/* Call the FDW's GetForeignPaths function to generate path(s) */
 	rel->fdwroutine->GetForeignPaths(root, rel, rte->relid);
-
-	/* Select cheapest path */
-	set_cheapest(rel);
 }
 
 /*
@@ -1272,9 +1271,6 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
 
 	/* Generate appropriate path */
 	add_path(rel, create_subqueryscan_path(root, rel, pathkeys, required_outer));
-
-	/* Select cheapest path (pretty easy in this case...) */
-	set_cheapest(rel);
 }
 
 /*
@@ -1343,9 +1339,6 @@ set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 	/* Generate appropriate path */
 	add_path(rel, create_functionscan_path(root, rel,
 										   pathkeys, required_outer));
-
-	/* Select cheapest path (pretty easy in this case...) */
-	set_cheapest(rel);
 }
 
 /*
@@ -1366,9 +1359,6 @@ set_values_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 
 	/* Generate appropriate path */
 	add_path(rel, create_valuesscan_path(root, rel, required_outer));
-
-	/* Select cheapest path (pretty easy in this case...) */
-	set_cheapest(rel);
 }
 
 /*
@@ -1435,9 +1425,6 @@ set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 
 	/* Generate appropriate path */
 	add_path(rel, create_ctescan_path(root, rel, required_outer));
-
-	/* Select cheapest path (pretty easy in this case...) */
-	set_cheapest(rel);
 }
 
 /*
@@ -1488,9 +1475,6 @@ set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 
 	/* Generate appropriate path */
 	add_path(rel, create_worktablescan_path(root, rel, required_outer));
-
-	/* Select cheapest path (pretty easy in this case...) */
-	set_cheapest(rel);
 }
 
 /*
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 4b641a2..9939ad8 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -77,13 +77,13 @@ 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 Plan *create_custom_plan(PlannerInfo *root, CustomPath *best_path);
 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,
 					  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);
@@ -261,6 +261,9 @@ create_plan_recurse(PlannerInfo *root, Path *best_path)
 			plan = create_unique_plan(root,
 									  (UniquePath *) best_path);
 			break;
+		case T_CustomPlan:
+			plan = create_custom_plan(root, (CustomPath *) best_path);
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) best_path->pathtype);
@@ -1072,6 +1075,97 @@ 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 'scan_clauses' and targetlist 'tlist'.
+ */
+static Plan *
+create_custom_plan(PlannerInfo *root, CustomPath *best_path)
+{
+	CustomPlan	   *custom_plan;
+	RelOptInfo	   *rel;
+	List		   *tlist = NIL;
+	List		   *clauses = NIL;
+
+	/*
+	 * Create a custom-plan object delivered from CustomPlan type,
+	 * according to the supplied CustomPath
+	 */
+	Assert(best_path->path.pathtype == T_CustomPlan ||
+		   best_path->path.pathtype == T_CustomPlanMarkPos);
+	custom_plan = (CustomPlan *)
+		best_path->methods->CreateCustomPlan(root, best_path);
+	Assert(nodeTag(custom_plan) == best_path->path.pathtype);
+
+	rel = best_path->path.parent;
+	if (rel)
+	{
+		if (rel->reloptkind == RELOPT_BASEREL)
+		{
+			Assert(rel->relid > 0);
+			custom_plan->scan.scanrelid = rel->relid;
+
+			/*
+			 * For table scans, rather than using the relation targetlist
+			 * (which is only those Vars actually needed by the query),
+			 * we prefer to generate a tlist containing all Vars in order.
+			 * This will allow the executor to optimize away projection of
+			 * the table tuples, if possible.
+			 */
+			if (use_physical_tlist(root, rel))
+				tlist = build_physical_tlist(root, rel);
+		}
+		/* elsewhere, we generate a tlist from the relation targetlist */
+		if (tlist == NIL)
+			tlist = build_path_tlist(root, &best_path->path);
+
+		/*
+		 * Extract the relevant restriction clauses from the parent relation.
+		 * The executor must apply all these restrictions during the scan,
+		 * except for pseudoconstants which we'll take care of below.
+		 */
+		clauses = rel->baserestrictinfo;
+
+		/*
+		 * If this is a parameterized scan, we also need to enforce all
+		 * the join clauses available from the outer relation(s).
+		 */
+		if (best_path->path.param_info)
+			clauses = list_concat(list_copy(clauses),
+								  best_path->path.param_info->ppi_clauses);
+
+		/* Sort clauses into best execution order */
+		clauses = order_qual_clauses(root, clauses);
+
+		/*
+		 * Replace outer-relation variables with nestloop params.
+		 * Note that any other clauses which is managed by extension
+		 * itself has to be handled in InitCustomPlan() method, as
+		 * built-in code doing.
+		 */
+		if (best_path->path.param_info)
+			clauses = (List *)replace_nestloop_params(root, (Node *)clauses);
+	}
+	/*
+	 * Copy cost data from Path to Plan; no need to make custom-plan
+	 * providers do this
+	 */
+	copy_path_costsize((Plan *)custom_plan, (Path *)best_path);
+
+	/*
+	 * Let the custom-plan provider perform its final initialization
+	 * of this CustomPlan (to be an inherited type, actually) node
+	 * according to its own necessity.
+	 * Note that custom-plan provider may/can replace (or stack another
+	 * one on) its own custom-plan node on demand, for example, to add
+	 * Result node to handle pseudo constant using create_gating_plan().
+	 */
+	return custom_plan->methods->InitCustomPlan(custom_plan,
+												root, best_path,
+												tlist, clauses);
+}
 
 /*****************************************************************************
  *
@@ -2540,7 +2634,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 4d717df..c416859 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,14 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 			}
 			break;
 
+		case T_CustomPlan:
+			{
+				CustomPlan *cplan = (CustomPlan *) plan;
+
+				cplan->methods->SetCustomPlanRef(root, cplan, rtoffset);
+			}
+			break;
+
 		case T_NestLoop:
 		case T_MergeJoin:
 		case T_HashJoin:
@@ -1158,7 +1165,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..9833073 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -2283,6 +2283,30 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
 			context.paramids = bms_add_members(context.paramids, scan_params);
 			break;
 
+		case T_CustomPlan:
+			{
+				CustomPlan *cplan = (CustomPlan *) plan;
+
+				/*
+				 * If this custom-plan scab a particular relation, we
+				 * adjust paramids as other scan derivered node.
+				 */
+				if (cplan->scan.scanrelid > 0)
+					context.paramids = bms_add_members(context.paramids,
+													   scan_params);
+				/*
+				 * custom plan provider is responsible to apply
+				 * finalize_primnode() on the expression node of its
+				 * private fields, but no need to apply tlist and
+				 * qual of Plan node (already done above).
+				 */
+				if (cplan->methods->FinalizeCustomPlan)
+					cplan->methods->FinalizeCustomPlan(root, cplan,
+													   finalize_primnode,
+													   (void *)&context);
+			}
+			break;
+
 		case T_ModifyTable:
 			{
 				ModifyTable *mtplan = (ModifyTable *) plan;
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index d129f8d..d6beb53 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -16,6 +16,10 @@
 
 #include <math.h>
 
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "catalog/pg_custom_plan_provider.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
@@ -25,8 +29,11 @@
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
+#include "utils/inval.h"
 #include "utils/lsyscache.h"
+#include "utils/memutils.h"
 #include "utils/selfuncs.h"
+#include "utils/syscache.h"
 
 
 typedef enum
@@ -2078,3 +2085,106 @@ reparameterize_path(PlannerInfo *root, Path *path,
 	}
 	return NULL;
 }
+
+/*****************************************************************************
+ *		creation of custom-plan paths
+ *****************************************************************************/
+static List *custom_scan_callchain = NIL;
+static bool custom_plan_callchain_is_ready = false;
+static MemoryContext custom_plan_memcxt = NULL;
+
+static void
+invalidate_custom_plan_callchain(Datum arg, int cacheid, uint32 hashvalue)
+{
+	MemoryContextReset(custom_plan_memcxt);
+	custom_plan_callchain_is_ready = false;
+	custom_scan_callchain = NIL;
+}
+
+static void
+setup_custom_plan_callchain(void)
+{
+	Relation		rel;
+	SysScanDesc		scan;
+	HeapTuple		tuple;
+	MemoryContext	oldcxt;
+
+	custom_scan_callchain = NIL;
+
+	rel = heap_open(CustomPlanProviderRelationId, AccessShareLock);
+
+	/* full scan on the pg_custom_plan once */
+	scan = systable_beginscan(rel, InvalidOid, false, NULL, 0, NULL);
+
+	oldcxt = MemoryContextSwitchTo(custom_plan_memcxt);
+	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+	{
+		Form_pg_custom_plan_provider	cppForm
+			= (Form_pg_custom_plan_provider) GETSTRUCT(tuple);
+
+		if (cppForm->cppclass == CUSTOMPLAN_CLASS_SCAN)
+		{
+			custom_scan_callchain = lappend_oid(custom_scan_callchain,
+												cppForm->cpphandler);
+		}
+		else
+			elog(LOG, "Bug? custom-plan provider \"%s\" has unknown class: %c",
+				 NameStr(cppForm->cppname), cppForm->cppclass);
+	}
+	MemoryContextSwitchTo(oldcxt);
+	systable_endscan(scan);
+
+	heap_close(rel, AccessShareLock);
+
+	custom_plan_callchain_is_ready = true;
+}
+
+static void
+init_custom_plan_callchain(void)
+{
+	/* memory context to keep callchain for custom-plans */
+	custom_plan_memcxt = AllocSetContextCreate(CacheMemoryContext,
+											   "custom plan memory context",
+											   ALLOCSET_DEFAULT_MINSIZE,
+											   ALLOCSET_DEFAULT_INITSIZE,
+											   ALLOCSET_DEFAULT_MAXSIZE);
+
+	/* flush cached callchain on catalog updates */
+	CacheRegisterSyscacheCallback(CUSTOMPLANPROVIDEROID,
+								  invalidate_custom_plan_callchain,
+								  (Datum) 0);
+	/* also, initial setting up */
+	setup_custom_plan_callchain();
+}
+
+/*
+ * call_custom_scan_providers
+ *
+ * A callchain on relation scan. custom-plan provider can add alternative
+ * scan paths derived from CustomPath class.
+ */
+void
+call_custom_scan_providers(PlannerInfo *root,
+						   RelOptInfo *baserel,
+						   RangeTblEntry *rte)
+{
+	customScanArg	sarg;
+	ListCell	   *cell;
+
+	if (!custom_plan_memcxt)
+		init_custom_plan_callchain();
+	else if (!custom_plan_callchain_is_ready)
+		setup_custom_plan_callchain();
+
+	Assert(custom_plan_callchain_is_ready);
+	sarg.custom_class = CUSTOMPLAN_CLASS_SCAN;
+	sarg.root = root;
+	sarg.baserel = baserel;
+	sarg.rte = rte;
+
+	foreach (cell, custom_scan_callchain)
+	{
+		(void) OidFunctionCall1(lfirst_oid(cell),
+								PointerGetDatum(&sarg));
+	}
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a113809..c8ea278 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -51,6 +51,7 @@
 
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/pg_custom_plan_provider.h"
 #include "catalog/pg_trigger.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -259,6 +260,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		DropOwnedStmt ReassignOwnedStmt
 		AlterTSConfigurationStmt AlterTSDictionaryStmt
 		CreateMatViewStmt RefreshMatViewStmt
+		CreateCustomPlanProviderStmt DropCustomPlanProviderStmt
 
 %type <node>	select_no_parens select_with_parens select_clause
 				simple_select values_clause
@@ -514,6 +516,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
 
+%type <ival>	cpp_class
+%type <defelt>	cpp_option
+%type <list>	opt_cpp_options cpp_options
+
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -549,7 +555,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
 	CROSS CSV CURRENT_P
 	CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
-	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
+	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CUSTOM CYCLE
 
 	DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
 	DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DESC
@@ -588,8 +594,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	OBJECT_P OF OFF OFFSET OIDS ON ONLY OPERATOR OPTION OPTIONS OR
 	ORDER ORDINALITY OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER
 
-	PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POSITION
-	PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
+	PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLAN PLANS POSITION
+	PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY PROVIDER
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROGRAM
 
 	QUOTE
@@ -599,8 +605,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK
 	ROW ROWS RULE
 
-	SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
-	SERIALIZABLE SERVER SESSION SESSION_USER SET SETOF SHARE
+	SAVEPOINT SCAN SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE
+	SEQUENCES SERIALIZABLE SERVER SESSION SESSION_USER SET SETOF SHARE
 	SHOW SIMILAR SIMPLE SMALLINT SNAPSHOT SOME STABLE STANDALONE_P START
 	STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBSTRING
 	SYMMETRIC SYSID SYSTEM_P
@@ -762,6 +768,7 @@ stmt :
 			| CreateAssertStmt
 			| CreateCastStmt
 			| CreateConversionStmt
+			| CreateCustomPlanProviderStmt
 			| CreateDomainStmt
 			| CreateExtensionStmt
 			| CreateFdwStmt
@@ -792,6 +799,7 @@ stmt :
 			| DoStmt
 			| DropAssertStmt
 			| DropCastStmt
+			| DropCustomPlanProviderStmt
 			| DropFdwStmt
 			| DropForeignServerStmt
 			| DropGroupStmt
@@ -8705,6 +8713,78 @@ CreateConversionStmt:
 			}
 		;
 
+/****************************************************************************
+ *
+ *	QUERY:
+ *			CREATE CUSTOM PLAN PROVIDER name FOR <class> <options>
+ *
+ ****************************************************************************/
+
+CreateCustomPlanProviderStmt:
+			CREATE CUSTOM PLAN PROVIDER name FOR cpp_class opt_cpp_options
+			{
+				CreateCustomPlanProviderStmt *n
+					= makeNode(CreateCustomPlanProviderStmt);
+				n->cpp_name = $5;
+				n->cpp_class = $7;
+				n->cpp_options = $8;
+				$$ = (Node *) n;
+			}
+		;
+
+cpp_class:
+			SCAN				{ $$ = CUSTOMPLAN_CLASS_SCAN; }
+		;
+
+cpp_option:
+			HANDLER handler_name
+			{
+				$$ = makeDefElem("handler", (Node *)$2);
+			}
+		;
+
+cpp_options:
+			cpp_option					{ $$ = list_make1($1); }
+			| cpp_options cpp_option	{ $$ = lappend($1, $2); }
+		;
+
+opt_cpp_options:
+			cpp_options			{ $$ = $1; }
+			| /* empty */		{ $$ = NIL; }
+		;
+
+/****************************************************************************
+ *
+ *     QUERY:
+ *             DROP CUSTOM PLAN PROVIDER name
+ *
+ ****************************************************************************/
+
+DropCustomPlanProviderStmt:
+			DROP CUSTOM PLAN PROVIDER name opt_drop_behavior
+			{
+				DropStmt *n = makeNode(DropStmt);
+				n->removeType = OBJECT_CPP;
+				n->objects = list_make1(list_make1(makeString($5)));
+				n->arguments = NIL;
+				n->missing_ok = false;
+				n->behavior = $6;
+				n->concurrent = false;
+				$$ = (Node *) n;
+			}
+		|	DROP CUSTOM PLAN PROVIDER IF_P EXISTS name opt_drop_behavior
+			{
+				DropStmt *n = makeNode(DropStmt);
+				n->removeType = OBJECT_CPP;
+				n->objects = list_make1(list_make1(makeString($7)));
+				n->arguments = NIL;
+				n->missing_ok = true;
+				n->behavior = $8;
+				n->concurrent = false;
+				$$ = (Node *) n;
+			}
+		;
+
 /*****************************************************************************
  *
  *		QUERY:
@@ -12914,6 +12994,7 @@ unreserved_keyword:
 			| CSV
 			| CURRENT_P
 			| CURSOR
+			| CUSTOM
 			| CYCLE
 			| DATA_P
 			| DATABASE
@@ -13025,6 +13106,7 @@ unreserved_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PLAN
 			| PLANS
 			| PRECEDING
 			| PREPARE
@@ -13035,6 +13117,7 @@ unreserved_keyword:
 			| PROCEDURAL
 			| PROCEDURE
 			| PROGRAM
+			| PROVIDER
 			| QUOTE
 			| RANGE
 			| READ
@@ -13060,6 +13143,7 @@ unreserved_keyword:
 			| ROWS
 			| RULE
 			| SAVEPOINT
+			| SCAN
 			| SCHEMA
 			| SCROLL
 			| SEARCH
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 07e0b98..cbfd3cf 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -202,6 +202,7 @@ check_xact_readonly(Node *parsetree)
 		case T_AlterTableSpaceOptionsStmt:
 		case T_AlterTableSpaceMoveStmt:
 		case T_CreateForeignTableStmt:
+		case T_CreateCustomPlanProviderStmt:
 		case T_ImportForeignSchemaStmt:
 		case T_SecLabelStmt:
 			PreventCommandIfReadOnly(CreateCommandTag(parsetree));
@@ -684,6 +685,14 @@ standard_ProcessUtility(Node *parsetree,
 			AlterEventTrigger((AlterEventTrigStmt *) parsetree);
 			break;
 
+		case  T_CreateCustomPlanProviderStmt:
+			{
+				CreateCustomPlanProviderStmt *cpp_stmt
+					= (CreateCustomPlanProviderStmt *)parsetree;
+				DefineCustomPlanProvider(cpp_stmt);
+			}
+			break;
+
 			/*
 			 * ******************************** ROLE statements ****
 			 */
@@ -1949,6 +1958,9 @@ CreateCommandTag(Node *parsetree)
 				case OBJECT_OPFAMILY:
 					tag = "DROP OPERATOR FAMILY";
 					break;
+				case OBJECT_CPP:
+					tag = "DROP CUSTOM PLAN PROVIDER";
+					break;
 				default:
 					tag = "???";
 			}
@@ -2216,6 +2228,10 @@ CreateCommandTag(Node *parsetree)
 			tag = "ALTER EVENT TRIGGER";
 			break;
 
+		case T_CreateCustomPlanProviderStmt:
+			tag = "CREATE CUSTOM PLAN PROVIDER";
+			break;
+
 		case T_CreatePLangStmt:
 			tag = "CREATE LANGUAGE";
 			break;
@@ -2840,6 +2856,10 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_CreateCustomPlanProviderStmt:
+			lev = LOGSTMT_DDL;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 0781ac8..abe27e2 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -5481,6 +5481,29 @@ 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-plan provider replaced a varno in expression tree by special
+ * varno.
+ */
+static Node *
+GetSpecialCustomVar(PlanState *ps, Var *varnode, PlanState **child_ps)
+{
+	CustomPlanState *cps = (CustomPlanState *) ps;
+
+	Assert(IsA(ps, CustomPlanState));
+	Assert(IS_SPECIAL_VARNO(varnode->varno));
+
+	if (!cps->methods->GetSpecialCustomVar)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("%s does not support special varno reference",
+						cps->methods->CustomName)));
+	return (Node *)cps->methods->GetSpecialCustomVar(cps, varnode, child_ps);
+}
 
 /*
  * Display a Var appropriately.
@@ -5510,6 +5533,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;
 
@@ -5534,6 +5559,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, CustomPlanState) &&
+			 (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;
@@ -5748,6 +5796,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;
 
@@ -5822,6 +5871,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, CustomPlanState) &&
+			 (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/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 94d951c..1619de7 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -32,6 +32,7 @@
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_conversion.h"
+#include "catalog/pg_custom_plan_provider.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_db_role_setting.h"
 #include "catalog/pg_default_acl.h"
@@ -345,6 +346,28 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		8
 	},
+	{CustomPlanProviderRelationId,	/* CUSTOMPLANPROVIDEROID */
+		CustomPlanProviderOidIndexId,
+		1,
+		{
+			ObjectIdAttributeNumber,
+			0,
+			0,
+			0
+		},
+		32
+	},
+	{CustomPlanProviderRelationId,	/* CUSTOMPLANPROVIDERNAME */
+		CustomPlanProviderNameIndexId,
+		1,
+		{
+			Anum_pg_custom_plan_provider_cppname,
+			0,
+			0,
+			0,
+		},
+		32
+	},
 	{DatabaseRelationId,		/* DATABASEOID */
 		DatabaseOidIndexId,
 		1,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 8ed2592..4a7186a 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -147,6 +147,7 @@ typedef enum ObjectClass
 	OCLASS_DEFACL,				/* pg_default_acl */
 	OCLASS_EXTENSION,			/* pg_extension */
 	OCLASS_EVENT_TRIGGER,		/* pg_event_trigger */
+	OCLASS_CPP,					/* pg_custom_plan_provider */
 	MAX_OCLASS					/* MUST BE LAST */
 } ObjectClass;
 
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 59576fd..b926e15 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -299,6 +299,12 @@ DECLARE_UNIQUE_INDEX(pg_extension_name_index, 3081, on pg_extension using btree(
 DECLARE_UNIQUE_INDEX(pg_range_rngtypid_index, 3542, on pg_range using btree(rngtypid oid_ops));
 #define RangeTypidIndexId					3542
 
+DECLARE_UNIQUE_INDEX(pg_custom_plan_provider_oid_index, 3563, on pg_custom_plan_provider using btree(oid oid_ops));
+#define CustomPlanProviderOidIndexId 3563
+
+DECLARE_UNIQUE_INDEX(pg_custom_plan_provider_name_index, 3564, on pg_custom_plan_provider using btree(cppname name_ops));
+#define CustomPlanProviderNameIndexId 3564
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_custom_plan_provider.h b/src/include/catalog/pg_custom_plan_provider.h
new file mode 100644
index 0000000..f68b4de
--- /dev/null
+++ b/src/include/catalog/pg_custom_plan_provider.h
@@ -0,0 +1,50 @@
+/* -------------------------------------------------------------------------
+ *
+ * pg_custom_plan_provider.h
+ *	definition of the system "custom plan provider" relation
+ *  (pg_custom_plan_provider)
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * -------------------------------------------------------------------------
+ */
+#ifndef PG_CUSTOM_PLAN_PROVIDER_H
+#define PG_CUSTOM_PLAN_PROVIDER_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *     pg_custom_plan_provider definition.  cpp turns this into
+ *     typedef struct FormData_pg_custom_plan_provider
+ * ----------------
+ */
+#define CustomPlanProviderRelationId       3562
+
+CATALOG(pg_custom_plan_provider,3562)
+{
+	NameData	cppname;		/* name of custom-plan provider */
+	char		cppclass;		/* class of custom-plan */
+	Oid			cpphandler;		/* function of custom-plan provider */
+} FormData_pg_custom_plan_provider;
+
+/* ----------------
+ *     Form_pg_custom_plan_provider corresponds to a pointer to a tuple
+ *     with the format of pg_custom_plan_provider relation.
+ * ----------------
+ */
+typedef FormData_pg_custom_plan_provider *Form_pg_custom_plan_provider;
+
+/* ----------------
+ *     compiler constants for pg_custom_plan_provider
+ * ----------------
+ */
+#define Natts_pg_custom_plan_provider			3
+#define Anum_pg_custom_plan_provider_cppname	1
+#define Anum_pg_custom_plan_provider_cppclass	2
+#define Anum_pg_custom_plan_provider_cpphandler	3
+
+/* definition of cppclass */
+#define CUSTOMPLAN_CLASS_SCAN			's'
+
+#endif	/* PG_CUSTOM_PLAN_PROVIDER_H */
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index f8b4a65..fc798a2 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");
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 0ebdbc1..b0f3f0c 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -130,6 +130,11 @@ extern Datum transformGenericOptions(Oid catalogId,
 						List *options,
 						Oid fdwvalidator);
 
+/* commands/custom_plan.c */
+extern Oid get_custom_plan_provider_oid(const char *cpp_name, bool missing_ok);
+extern void	RemoveCustomPlanProviderById(Oid cust_oid);
+extern Oid	DefineCustomPlanProvider(CreateCustomPlanProviderStmt *stmt);
+
 /* support routines in commands/define.c */
 
 extern char *defGetString(DefElem *def);
diff --git a/src/include/executor/nodeCustom.h b/src/include/executor/nodeCustom.h
new file mode 100644
index 0000000..abe1e94
--- /dev/null
+++ b/src/include/executor/nodeCustom.h
@@ -0,0 +1,30 @@
+/* ------------------------------------------------------------------------
+ *
+ * nodeCustom.h
+ *
+ * prototypes for CustomPlan 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 CustomPlanState *ExecInitCustomPlan(CustomPlan *cplan,
+										   EState *estate, int eflags);
+extern TupleTableSlot *ExecCustomPlan(CustomPlanState *node);
+extern Node *MultiExecCustomPlan(CustomPlanState *node);
+extern void ExecEndCustomPlan(CustomPlanState *node);
+
+extern void ExecReScanCustomPlan(CustomPlanState *node);
+extern void ExecCustomMarkPos(CustomPlanState *node);
+extern void ExecCustomRestrPos(CustomPlanState *node);
+
+#endif	/* NODECUSTOM_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index b271f21..26e6b33 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1504,6 +1504,48 @@ typedef struct ForeignScanState
 	void	   *fdw_state;		/* foreign-data wrapper can keep state here */
 } ForeignScanState;
 
+/* ----------------
+ * CustomPlanState information
+ *
+ *		CustomPlan nodes are used to execute custom code within executor.
+ * ----------------
+ */
+struct CustomExecMethods;
+struct ExplainState;	/* to avoid to include explain.h here */
+
+typedef struct CustomPlanState
+{
+	ScanState	ss;
+	const struct CustomExecMethods *methods;
+} CustomPlanState;
+
+typedef struct CustomExecMethods
+{
+	const char     *CustomName;
+
+	/* EXECUTOR methods */
+	void    (*BeginCustomPlan)(CustomPlanState *node,
+							   EState *estate,
+							   int eflags);
+	TupleTableSlot *(*ExecCustomPlan)(CustomPlanState *node);
+	Node   *(*MultiExecCustomPlan)(CustomPlanState *node);
+	void	(*EndCustomPlan)(CustomPlanState *node);
+	void	(*ReScanCustomPlan)(CustomPlanState *node);
+	void	(*MarkPosCustomPlan)(CustomPlanState *node);
+	void	(*RestrPosCustomPlan)(CustomPlanState *node);
+
+	/* EXPLAIN support */
+	void	(*ExplainCustomPlanTargetRel)(CustomPlanState *node,
+										  struct ExplainState *es);
+	void    (*ExplainCustomPlan)(CustomPlanState *node,
+								 List *ancestors,
+								 struct ExplainState *es);
+	void	(*ExplainCustomPreScanNode)(CustomPlanState *node,
+										Bitmapset **rels_used);
+	Node   *(*GetSpecialCustomVar)(CustomPlanState *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 067c768..d5fafbd 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -62,6 +62,8 @@ typedef enum NodeTag
 	T_CteScan,
 	T_WorkTableScan,
 	T_ForeignScan,
+	T_CustomPlan,
+	T_CustomPlanMarkPos,
 	T_Join,
 	T_NestLoop,
 	T_MergeJoin,
@@ -107,6 +109,7 @@ typedef enum NodeTag
 	T_CteScanState,
 	T_WorkTableScanState,
 	T_ForeignScanState,
+	T_CustomPlanState,
 	T_JoinState,
 	T_NestLoopState,
 	T_MergeJoinState,
@@ -224,6 +227,7 @@ typedef enum NodeTag
 	T_HashPath,
 	T_TidPath,
 	T_ForeignPath,
+	T_CustomPath,
 	T_AppendPath,
 	T_MergeAppendPath,
 	T_ResultPath,
@@ -366,6 +370,7 @@ typedef enum NodeTag
 	T_RefreshMatViewStmt,
 	T_ReplicaIdentityStmt,
 	T_AlterSystemStmt,
+	T_CreateCustomPlanProviderStmt,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8364bef..1f98cce 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1209,6 +1209,7 @@ typedef enum ObjectType
 	OBJECT_CONSTRAINT,
 	OBJECT_COLLATION,
 	OBJECT_CONVERSION,
+	OBJECT_CPP,
 	OBJECT_DATABASE,
 	OBJECT_DOMAIN,
 	OBJECT_EVENT_TRIGGER,
@@ -2073,6 +2074,18 @@ typedef struct AlterOpFamilyStmt
 } AlterOpFamilyStmt;
 
 /* ----------------------
+ *     Create Custom Plan Provider Statement
+ * ----------------------
+ */
+typedef struct CreateCustomPlanProviderStmt
+{
+	NodeTag     type;
+	char	   *cpp_name;		/* name of custom-plan provider */
+	char		cpp_class;		/* class of custom-plan provides */
+	List	   *cpp_options;	/* generic options for provider */
+} CreateCustomPlanProviderStmt;
+
+/* ----------------------
  *		Drop Table|Sequence|View|Index|Type|Domain|Conversion|Schema Statement
  * ----------------------
  */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 3b9c683..b48c735 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -15,6 +15,7 @@
 #define PLANNODES_H
 
 #include "access/sdir.h"
+#include "lib/stringinfo.h"
 #include "nodes/bitmapset.h"
 #include "nodes/primnodes.h"
 
@@ -479,6 +480,42 @@ typedef struct ForeignScan
 	bool		fsSystemCol;	/* true if any "system column" is needed */
 } ForeignScan;
 
+/* ----------------
+ *     CustomPlan node
+ * ----------------
+ */
+struct CustomPlanMethods;
+
+struct CustomPath;			/* to avoid to include nodes/relation.h here */
+struct PlannerInfo;			/* to avoid to include nodes/relation.h here */
+
+typedef struct CustomPlan
+{
+	Scan		scan;
+	const struct CustomPlanMethods *methods;
+} CustomPlan;
+
+typedef struct CustomPlanMethods
+{
+	const char *CustomName;
+	Plan	  *(*InitCustomPlan)(CustomPlan *custom_plan,
+								 struct PlannerInfo *root,
+								 struct CustomPath *best_path,
+								 List *tlist,
+								 List *clauses);
+	void	   (*SetCustomPlanRef)(struct PlannerInfo *root,
+								   CustomPlan *custom_plan,
+								   int rtoffset);
+	bool	   (*SupportBackwardScan)(CustomPlan *custom_plan);
+	void	   (*FinalizeCustomPlan)(struct PlannerInfo *root,
+									 CustomPlan *custom_plan,
+									 bool (*finalize_primnode)(),
+									 void *finalize_context);
+	Node	  *(*CreateCustomPlanState)(CustomPlan *custom_plan);
+	void	   (*TextOutCustomPlan)(StringInfo str,
+									const CustomPlan *node);
+	CustomPlan *(*CopyCustomPlan)(const CustomPlan *from);
+} CustomPlanMethods;
 
 /*
  * ==========
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index dacbe9c..a65bb0c 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"
@@ -881,6 +882,31 @@ 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;
+
+typedef struct CustomPath
+{
+	Path        path;
+	const struct CustomPathMethods *methods;
+} CustomPath;
+
+typedef struct CustomPathMethods
+{
+	const char *CustomName;
+	Node   *(*CreateCustomPlan)(PlannerInfo *root,
+								CustomPath *best_path);
+	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..df4d6e8 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -129,6 +129,20 @@ extern Path *reparameterize_path(PlannerInfo *root, Path *path,
 					double loop_count);
 
 /*
+ * interface towards custom-plan provider functions
+ */
+typedef struct {
+	uint32			custom_class;
+	PlannerInfo	   *root;
+	RelOptInfo	   *baserel;
+	RangeTblEntry  *rte;
+} customScanArg;
+
+extern void call_custom_scan_providers(PlannerInfo *root,
+									   RelOptInfo *baserel,
+									   RangeTblEntry *rte);
+
+/*
  * prototypes for relnode.c
  */
 extern void setup_simple_rel_arrays(PlannerInfo *root);
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 8bdb7db..76e3c86 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
@@ -128,6 +129,7 @@ extern List *remove_useless_joins(PlannerInfo *root, List *joinlist);
  */
 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 set_opfuncid(OpExpr *opexpr);
 extern void set_sa_opfuncid(ScalarArrayOpExpr *opexpr);
 extern void record_plan_function_dependency(PlannerInfo *root, Oid funcid);
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index b52e507..05603e5 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -107,6 +107,7 @@ PG_KEYWORD("current_time", CURRENT_TIME, RESERVED_KEYWORD)
 PG_KEYWORD("current_timestamp", CURRENT_TIMESTAMP, RESERVED_KEYWORD)
 PG_KEYWORD("current_user", CURRENT_USER, RESERVED_KEYWORD)
 PG_KEYWORD("cursor", CURSOR, UNRESERVED_KEYWORD)
+PG_KEYWORD("custom", CUSTOM, UNRESERVED_KEYWORD)
 PG_KEYWORD("cycle", CYCLE, UNRESERVED_KEYWORD)
 PG_KEYWORD("data", DATA_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("database", DATABASE, UNRESERVED_KEYWORD)
@@ -282,6 +283,7 @@ PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD)
 PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD)
+PG_KEYWORD("plan", PLAN, UNRESERVED_KEYWORD)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD)
 PG_KEYWORD("position", POSITION, COL_NAME_KEYWORD)
 PG_KEYWORD("preceding", PRECEDING, UNRESERVED_KEYWORD)
@@ -295,6 +297,7 @@ PG_KEYWORD("privileges", PRIVILEGES, UNRESERVED_KEYWORD)
 PG_KEYWORD("procedural", PROCEDURAL, UNRESERVED_KEYWORD)
 PG_KEYWORD("procedure", PROCEDURE, UNRESERVED_KEYWORD)
 PG_KEYWORD("program", PROGRAM, UNRESERVED_KEYWORD)
+PG_KEYWORD("provider", PROVIDER, UNRESERVED_KEYWORD)
 PG_KEYWORD("quote", QUOTE, UNRESERVED_KEYWORD)
 PG_KEYWORD("range", RANGE, UNRESERVED_KEYWORD)
 PG_KEYWORD("read", READ, UNRESERVED_KEYWORD)
@@ -325,6 +328,7 @@ PG_KEYWORD("row", ROW, COL_NAME_KEYWORD)
 PG_KEYWORD("rows", ROWS, UNRESERVED_KEYWORD)
 PG_KEYWORD("rule", RULE, UNRESERVED_KEYWORD)
 PG_KEYWORD("savepoint", SAVEPOINT, UNRESERVED_KEYWORD)
+PG_KEYWORD("scan", SCAN, UNRESERVED_KEYWORD)
 PG_KEYWORD("schema", SCHEMA, UNRESERVED_KEYWORD)
 PG_KEYWORD("scroll", SCROLL, UNRESERVED_KEYWORD)
 PG_KEYWORD("search", SEARCH, UNRESERVED_KEYWORD)
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index f97229f..e1e56f7 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -52,6 +52,8 @@ enum SysCacheIdentifier
 	CONNAMENSP,
 	CONSTROID,
 	CONVOID,
+	CUSTOMPLANPROVIDEROID,
+	CUSTOMPLANPROVIDERNAME,
 	DATABASEOID,
 	DEFACLROLENSPOBJ,
 	ENUMOID,
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 111d24c..5ebceea 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -97,6 +97,7 @@ pg_class|t
 pg_collation|t
 pg_constraint|t
 pg_conversion|t
+pg_custom_plan_provider|t
 pg_database|t
 pg_db_role_setting|t
 pg_default_acl|t
#11Shigeru Hanada
shigeru.hanada@gmail.com
In reply to: Kouhei Kaigai (#10)

Kaigai-san,

2014-07-15 21:37 GMT+09:00 Kouhei Kaigai <kaigai@ak.jp.nec.com>:

Sorry, expected result of sanity-check test was not updated on
renaming to pg_custom_plan_provider.
The attached patch fixed up this point.

I confirmed that all regression tests passed, so I marked the patch as
"Ready for committer".

--
Shigeru HANADA

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

#12Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Kouhei Kaigai (#10)

I haven't followed this at all, but I just skimmed over it and noticed
the CustomPlanMarkPos thingy; apologies if this has been discussed
before. It seems a bit odd to me; why isn't it sufficient to have a
boolean flag in regular CustomPlan to indicate that it supports
mark/restore?

--
�lvaro Herrera 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

#13Andres Freund
andres@2ndquadrant.com
In reply to: Shigeru Hanada (#11)

On 2014-07-16 10:43:08 +0900, Shigeru Hanada wrote:

Kaigai-san,

2014-07-15 21:37 GMT+09:00 Kouhei Kaigai <kaigai@ak.jp.nec.com>:

Sorry, expected result of sanity-check test was not updated on
renaming to pg_custom_plan_provider.
The attached patch fixed up this point.

I confirmed that all regression tests passed, so I marked the patch as
"Ready for committer".

I personally don't see how this patch is 'ready for committer'. I
realize that that state is sometimes used to denote that review needs to
be "escalated", but it still seemspremature.

Unless I miss something there hasn't been any API level review of this?
Also, aren't there several open items?

Perhaps there needs to be a stage between 'needs review' and 'ready for
committer'?

Greetings,

Andres Freund

--
Andres Freund 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

#14Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#12)

Alvaro Herrera <alvherre@2ndquadrant.com> writes:

I haven't followed this at all, but I just skimmed over it and noticed
the CustomPlanMarkPos thingy; apologies if this has been discussed
before. It seems a bit odd to me; why isn't it sufficient to have a
boolean flag in regular CustomPlan to indicate that it supports
mark/restore?

Yeah, I thought that was pretty bogus too, but it's well down the
list of issues that were there last time I looked at this ...

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

#15Kouhei Kaigai
kaigai@ak.jp.nec.com
In reply to: Andres Freund (#13)

I personally don't see how this patch is 'ready for committer'. I realize
that that state is sometimes used to denote that review needs to be
"escalated", but it still seemspremature.

Unless I miss something there hasn't been any API level review of this?
Also, aren't there several open items?

Even though some interface specifications are revised according to the
comment from Tom on the last development cycle, the current set of
interfaces are not reviewed by committers. I really want this.

Here are two open items that we want to wait for committers comments.

* Whether set_cheapest() is called for all relkind?

This pactch moved set_cheapest() to the end of set_rel_pathlist(),
to consolidate entrypoint of custom-plan-provider handler function.
It also implies CPP can provider alternative paths towards non-regular
relations (like sub-queries, functions, ...).
Hanada-san wonder whether we really have a case to run alternative
sub-query code. Even though I don't have usecases for alternative
sub-query execution logic, but we also don't have a reason why not
to restrict it.

* How argument of add_path handler shall be derivered?

The handler function (that adds custom-path to the required relation
scan if it can provide) is declared with an argument with INTERNAL
data type. Extension needs to have type-cast on the supplied pointer
to customScanArg data-type (or potentially customHashJoinArg and
so on...) according to the custom plan class.
I think it is well extendable design than strict argument definitions,
but Hanada-san wonder whether it is the best design.

Perhaps there needs to be a stage between 'needs review' and 'ready for
committer'?

It needs clarification of 'ready for committer'. I think interface
specification is a kind of task to be discussed with committers
because preference/viewpoint of rr-reviewer are not always same
opinion with them.

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

-----Original Message-----
From: Andres Freund [mailto:andres@2ndquadrant.com]
Sent: Friday, July 18, 2014 3:12 AM
To: Shigeru Hanada
Cc: Kaigai Kouhei(海外 浩平); Kohei KaiGai; Simon Riggs; Tom Lane; Stephen
Frost; Robert Haas; PgHacker; Jim Mlodgenski; Peter Eisentraut
Subject: Re: [HACKERS] [v9.5] Custom Plan API

On 2014-07-16 10:43:08 +0900, Shigeru Hanada wrote:

Kaigai-san,

2014-07-15 21:37 GMT+09:00 Kouhei Kaigai <kaigai@ak.jp.nec.com>:

Sorry, expected result of sanity-check test was not updated on
renaming to pg_custom_plan_provider.
The attached patch fixed up this point.

I confirmed that all regression tests passed, so I marked the patch as
"Ready for committer".

I personally don't see how this patch is 'ready for committer'. I realize
that that state is sometimes used to denote that review needs to be
"escalated", but it still seemspremature.

Unless I miss something there hasn't been any API level review of this?
Also, aren't there several open items?

Perhaps there needs to be a stage between 'needs review' and 'ready for
committer'?

Greetings,

Andres Freund

--
Andres Freund 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

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

Alvaro Herrera <alvherre@2ndquadrant.com> writes:

I haven't followed this at all, but I just skimmed over it and noticed
the CustomPlanMarkPos thingy; apologies if this has been discussed
before. It seems a bit odd to me; why isn't it sufficient to have a
boolean flag in regular CustomPlan to indicate that it supports
mark/restore?

Yeah, I thought that was pretty bogus too, but it's well down the list of
issues that were there last time I looked at this ...

IIRC, CustomPlanMarkPos was suggested to keep the interface of
ExecSupportsMarkRestore() that takes plannode tag to determine
whether it support Mark/Restore.
As my original proposition did, it seems to me a flag field in
CustomPlan structure is straightforward, if we don't hesitate to
change ExecSupportsMarkRestore().

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

#17Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Kouhei Kaigai (#16)
1 attachment(s)

2014-07-18 10:28 GMT+09:00 Kouhei Kaigai <kaigai@ak.jp.nec.com>:

Alvaro Herrera <alvherre@2ndquadrant.com> writes:

I haven't followed this at all, but I just skimmed over it and noticed
the CustomPlanMarkPos thingy; apologies if this has been discussed
before. It seems a bit odd to me; why isn't it sufficient to have a
boolean flag in regular CustomPlan to indicate that it supports
mark/restore?

Yeah, I thought that was pretty bogus too, but it's well down the list of
issues that were there last time I looked at this ...

IIRC, CustomPlanMarkPos was suggested to keep the interface of
ExecSupportsMarkRestore() that takes plannode tag to determine
whether it support Mark/Restore.
As my original proposition did, it seems to me a flag field in
CustomPlan structure is straightforward, if we don't hesitate to
change ExecSupportsMarkRestore().

The attached patch revised the above point.
It eliminates CustomPlanMarkPos, and adds flags field on CustomXXX
structure to inform the backend whether the custom plan provider can
support mark-restore position and backward scan.
This change requires ExecSupportsMarkRestore() to reference
contents of Path node, not only node-tag, so its declaration was also
changed to take a pointer to Path node.
The only caller of this function is final_cost_mergejoin() right now.
It just gives pathtype field of Path node on its invocation, so this change
does not lead significant degradation.

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

Attachments:

pgsql-v9.5-custom-plan.v6.patchapplication/octet-stream; name=pgsql-v9.5-custom-plan.v6.patchDownload
 contrib/Makefile                                  |   1 +
 contrib/ctidscan/Makefile                         |  19 +
 contrib/ctidscan/ctidscan--1.0.sql                |  12 +
 contrib/ctidscan/ctidscan--unpackaged-1.0.sql     |   0
 contrib/ctidscan/ctidscan.c                       | 944 ++++++++++++++++++++++
 contrib/ctidscan/ctidscan.control                 |   5 +
 contrib/ctidscan/expected/ctidscan.out            | 312 +++++++
 contrib/ctidscan/sql/ctidscan.sql                 |  54 ++
 doc/src/sgml/catalogs.sgml                        |  59 ++
 doc/src/sgml/custom-plan-provider.sgml            | 413 ++++++++++
 doc/src/sgml/filelist.sgml                        |   1 +
 doc/src/sgml/postgres.sgml                        |   1 +
 doc/src/sgml/ref/allfiles.sgml                    |   2 +
 doc/src/sgml/ref/create_custom_plan_provider.sgml | 139 ++++
 doc/src/sgml/ref/drop_custom_plan_provider.sgml   | 108 +++
 doc/src/sgml/reference.sgml                       |   2 +
 src/backend/catalog/Makefile                      |   2 +-
 src/backend/catalog/dependency.c                  |  11 +-
 src/backend/catalog/objectaddress.c               |  63 ++
 src/backend/commands/Makefile                     |   2 +-
 src/backend/commands/custom_plan.c                | 188 +++++
 src/backend/commands/dropcmds.c                   |   5 +
 src/backend/commands/event_trigger.c              |   2 +
 src/backend/commands/explain.c                    |  39 +
 src/backend/executor/Makefile                     |   2 +-
 src/backend/executor/execAmi.c                    |  38 +-
 src/backend/executor/execProcnode.c               |  18 +
 src/backend/executor/nodeCustom.c                 | 147 ++++
 src/backend/nodes/copyfuncs.c                     |  35 +
 src/backend/nodes/equalfuncs.c                    |  14 +
 src/backend/nodes/outfuncs.c                      |  27 +
 src/backend/optimizer/path/allpaths.c             |  30 +-
 src/backend/optimizer/path/costsize.c             |   2 +-
 src/backend/optimizer/plan/createplan.c           |  97 ++-
 src/backend/optimizer/plan/setrefs.c              |  11 +-
 src/backend/optimizer/plan/subselect.c            |  24 +
 src/backend/optimizer/util/pathnode.c             | 110 +++
 src/backend/parser/gram.y                         |  94 ++-
 src/backend/tcop/utility.c                        |  20 +
 src/backend/utils/adt/ruleutils.c                 |  73 ++
 src/backend/utils/cache/syscache.c                |  23 +
 src/include/catalog/dependency.h                  |   1 +
 src/include/catalog/indexing.h                    |   6 +
 src/include/catalog/pg_custom_plan_provider.h     |  56 ++
 src/include/catalog/pg_operator.h                 |   3 +
 src/include/commands/defrem.h                     |   5 +
 src/include/executor/executor.h                   |   2 +-
 src/include/executor/nodeCustom.h                 |  30 +
 src/include/nodes/execnodes.h                     |  44 +
 src/include/nodes/nodes.h                         |   4 +
 src/include/nodes/parsenodes.h                    |  13 +
 src/include/nodes/plannodes.h                     |  37 +
 src/include/nodes/relation.h                      |  27 +
 src/include/optimizer/pathnode.h                  |  14 +
 src/include/optimizer/planmain.h                  |   2 +
 src/include/parser/kwlist.h                       |   4 +
 src/include/utils/syscache.h                      |   2 +
 src/test/regress/expected/sanity_check.out        |   1 +
 58 files changed, 3360 insertions(+), 40 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..1e476a6
--- /dev/null
+++ b/contrib/ctidscan/Makefile
@@ -0,0 +1,19 @@
+# contrib/ctidscan/Makefile
+
+MODULES = ctidscan
+
+EXTENSION = ctidscan
+DATA = ctidscan--1.0.sql
+
+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--1.0.sql b/contrib/ctidscan/ctidscan--1.0.sql
new file mode 100644
index 0000000..46420a0
--- /dev/null
+++ b/contrib/ctidscan/ctidscan--1.0.sql
@@ -0,0 +1,12 @@
+--
+-- Create ctidscan handler function
+--
+CREATE FUNCTION ctidscanaddpath(internal)
+  RETURNS pg_catalog.void
+  AS 'MODULE_PATHNAME','CtidScanAddPath'
+  LANGUAGE C STRICT;
+
+--
+-- Create a custom-plan provider
+--
+CREATE CUSTOM PLAN PROVIDER ctidscan FOR scan HANDLER ctidscanaddpath;
diff --git a/contrib/ctidscan/ctidscan--unpackaged-1.0.sql b/contrib/ctidscan/ctidscan--unpackaged-1.0.sql
new file mode 100644
index 0000000..e69de29
diff --git a/contrib/ctidscan/ctidscan.c b/contrib/ctidscan/ctidscan.c
new file mode 100644
index 0000000..5a54fea
--- /dev/null
+++ b/contrib/ctidscan/ctidscan.c
@@ -0,0 +1,944 @@
+/*
+ * 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_custom_plan_provider.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 {
+	CustomPlan		cplan;
+	List		   *ctid_quals;
+} CtidScanPlan;
+
+typedef struct {
+	CustomPlanState	cps;
+	List		   *ctid_quals;		/* list of ExprState for inequality ops */
+} CtidScanState;
+
+static CustomPathMethods	ctidscan_path_methods;
+static CustomPlanMethods	ctidscan_plan_methods;
+static CustomExecMethods	ctidscan_exec_methods;
+
+/* function declarations */
+void	_PG_init(void);
+Datum	CtidScanAddPath(PG_FUNCTION_ARGS);
+
+#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;
+}
+
+/*
+ * CreateCtidScanPlan - A method of CustomPath; that populate a custom
+ * object being delivered from CustomPlan type, according to the supplied
+ * CustomPath object.
+ */
+static Node *
+CreateCtidScanPlan(PlannerInfo *root, CustomPath *best_path)
+{
+	CtidScanPath   *ctid_path = (CtidScanPath *) best_path;
+	CtidScanPlan   *ctid_scan;
+
+	ctid_scan = palloc0(sizeof(CtidScanPlan));
+	NodeSetTag(ctid_scan, T_CustomPlan);
+	ctid_scan->cplan.methods = &ctidscan_plan_methods;
+	ctid_scan->cplan.flags = ctid_path->cpath.flags;
+	ctid_scan->ctid_quals = ctid_path->ctid_quals;
+
+	return (Node *)&ctid_scan->cplan;
+}
+
+/*
+ * InitCtidScanPlan
+ *
+ * main portion to initialize CustomPlan node.
+ */
+static Plan *
+InitCtidScanPlan(CustomPlan *custom_plan,
+				 PlannerInfo *root, CustomPath *best_path,
+				 List *tlist, List *clauses)
+{
+	CtidScanPlan   *ctid_scan = (CtidScanPlan *)custom_plan;
+	List		   *ctid_quals = ((CtidScanPath *)best_path)->ctid_quals;
+
+	Assert(ctid_scan->cplan.scan.scanrelid > 0);
+
+	/* Set targetlist as is */
+	ctid_scan->cplan.scan.plan.targetlist = tlist;
+	/* Reduce RestrictInfo list to bare expressions */
+	ctid_scan->cplan.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 &ctid_scan->cplan.scan.plan;
+}
+
+/*
+ * 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 CustomPlan; 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,
+				   CustomPlan *custom_plan,
+				   int rtoffset)
+{
+	CtidScanPlan   *ctidscan = (CtidScanPlan *) custom_plan;
+	Scan		   *scan = &ctidscan->cplan.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 CustomPlan; that handles callbacks
+ * by finalize_plan().
+ */
+static void
+FinalizeCtidScanPlan(PlannerInfo *root,
+					 CustomPlan *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 CustomPlan; that populate a custom
+ * object being delivered from CustomPlanState type, according to the
+ * supplied CustomPath object.
+ */
+static Node *
+CreateCtidScanState(CustomPlan *custom_plan)
+{
+	CtidScanState  *ctss = palloc0(sizeof(CtidScanState));
+
+	NodeSetTag(ctss, T_CustomPlanState);
+	ctss->cps.methods = &ctidscan_exec_methods;
+	ctss->cps.flags = custom_plan->flags;
+
+	return (Node *)&ctss->cps;
+}
+
+/*
+ * TextOutCtidScanPlan - A method of CustomPlan; that generates text
+ * representation of the given object.
+ */
+static void
+TextOutCtidScanPlan(StringInfo str, const CustomPlan *node)
+{
+	CtidScanPlan   *ctid_plan = (CtidScanPlan *) node;
+
+	appendStringInfo(str, " :ctid_quals %s",
+					 nodeToString(ctid_plan->ctid_quals));
+}
+
+/*
+ * CopyCtidScanPlan - A method of CustomPlan; that create a copied object.
+ */
+static CustomPlan *
+CopyCtidScanPlan(const CustomPlan *from)
+{
+	CtidScanPlan   *oldnode = (CtidScanPlan *) from;
+	CtidScanPlan   *newnode = palloc0(sizeof(CtidScanPlan));
+
+	NodeSetTag(newnode, T_CustomPlan);
+	newnode->cplan.methods = oldnode->cplan.methods;
+	newnode->ctid_quals = copyObject(oldnode->ctid_quals);
+
+	return &newnode->cplan;
+}
+
+/*
+ * BeginCtidScan - A method of CustomPlanState; that initializes
+ * the supplied CtidScanState object, at beginning of the executor.
+ */
+static void
+BeginCtidScan(CustomPlanState *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->cps.ss.ss_currentScanDesc = NULL;
+}
+
+/*
+ * ReScanCtidScan - A method of CustomPlanState; that rewind the current
+ * seek position.
+ */
+static void
+ReScanCtidScan(CustomPlanState *node)
+{
+	CtidScanState  *ctss = (CtidScanState *)node;
+	HeapScanDesc	scan = ctss->cps.ss.ss_currentScanDesc;
+	EState		   *estate = node->ss.ps.state;
+	ScanDirection	direction = estate->es_direction;
+	Relation		relation = ctss->cps.ss.ss_currentRelation;
+	ExprContext	   *econtext = ctss->cps.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->cps.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->cps.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->cps.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(CustomPlanState *node)
+{
+	CtidScanState  *ctss = (CtidScanState *) node;
+	HeapScanDesc	scan;
+	TupleTableSlot *slot;
+	EState		   *estate = node->ss.ps.state;
+	ScanDirection	direction = estate->es_direction;
+	HeapTuple		tuple;
+
+	if (!ctss->cps.ss.ss_currentScanDesc)
+		ReScanCtidScan(node);
+	scan = ctss->cps.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->cps.ss.ss_ScanTupleSlot;
+	ExecStoreTuple(tuple, slot, scan->rs_cbuf, false);
+
+	return slot;
+}
+
+static bool
+CTidRecheckCustomScan(CustomPlanState *node, TupleTableSlot *slot)
+{
+	return true;
+}
+
+/*
+ * ExecCtidScan - A method of CustomPlanState; that fetches a tuple
+ * from the relation, if exist anymore.
+ */
+static TupleTableSlot *
+ExecCtidScan(CustomPlanState *node)
+{
+	return ExecScan(&node->ss,
+					(ExecScanAccessMtd) CTidAccessCustomScan,
+					(ExecScanRecheckMtd) CTidRecheckCustomScan);
+}
+
+/*
+ * CTidEndCustomScan - A method of CustomPlanState; that closes heap and
+ * scan descriptor, and release other related resources.
+ */
+static void
+EndCtidScan(CustomPlanState *node)
+{
+	CtidScanState  *ctss = (CtidScanState *)node;
+
+	if (ctss->cps.ss.ss_currentScanDesc)
+		heap_endscan(ctss->cps.ss.ss_currentScanDesc);
+}
+
+/*
+ * ExplanCtidScanTargetRel - A method of CustomPlanState; that output
+ * relation's name to be scanned.
+ */
+static void
+ExplanCtidScanTargetRel(CustomPlanState *node, ExplainState *es)
+{
+	CtidScanState  *ctss = (CtidScanState *) node;
+	CtidScanPlan   *ctid_plan = (CtidScanPlan *) ctss->cps.ss.ps.plan;
+	Index			rti = ctid_plan->cplan.scan.scanrelid;
+	RangeTblEntry   *rte;
+	char		   *objectname = NULL;
+	char		   *namespace = NULL;
+	char		   *refname;
+
+	/* logic copied from ExplainTargetRel */
+	rte = rt_fetch(rti, es->rtable);
+	refname = (char *) list_nth(es->rtable_names, rti - 1);
+	if (refname == NULL)
+		refname = rte->eref->aliasname;
+
+	Assert(rte->rtekind == RTE_RELATION);
+	objectname = get_rel_name(rte->relid);
+	if (es->verbose)
+		namespace = get_namespace_name(get_rel_namespace(rte->relid));
+
+	if (es->format == EXPLAIN_FORMAT_TEXT)
+	{
+		appendStringInfoString(es->str, " on");
+		if (namespace != NULL)
+			appendStringInfo(es->str, " %s.%s", quote_identifier(namespace),
+							 quote_identifier(objectname));
+		else if (objectname != NULL)
+			appendStringInfo(es->str, " %s", quote_identifier(objectname));
+		if (objectname == NULL || strcmp(refname, objectname) != 0)
+			appendStringInfo(es->str, " %s", quote_identifier(refname));
+	}
+	else
+	{
+		if (objectname != NULL)
+			ExplainPropertyText("Relation Name", objectname, es);
+		if (namespace != NULL)
+			ExplainPropertyText("Schema", namespace, es);
+		ExplainPropertyText("Alias", refname, es);
+	}
+}
+
+/*
+ * ExplainCtidScan - A method of CustomPlanState; that shows extra info
+ * on EXPLAIN command.
+ */
+static void
+ExplainCtidScan(CustomPlanState *node, List *ancestors, ExplainState *es)
+{
+	CtidScanState  *ctss = (CtidScanState *) node;
+	CtidScanPlan   *ctid_plan = (CtidScanPlan *) ctss->cps.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);
+	}
+}
+
+/*
+ * ExplainCtidPreScanNode - A method of CustomPlanState; that informs
+ * the core backend relation's rtindex to be referenced, prior to the
+ * main EXPLAIN processing.
+ */
+static void
+ExplainCtidPreScanNode(CustomPlanState *node, Bitmapset **rels_used)
+{
+	CtidScanState  *ctss = (CtidScanState *) node;
+	Index			scanrelid = ((Scan *)ctss->cps.ss.ps.plan)->scanrelid;
+
+	*rels_used = bms_add_member(*rels_used, scanrelid);
+}
+
+/*
+ * Entrypoint of this extension
+ */
+Datum
+CtidScanAddPath(PG_FUNCTION_ARGS)
+{
+	customScanArg  *cscan_arg = (customScanArg *)PG_GETARG_POINTER(0);
+	PlannerInfo	   *root;
+	RangeTblEntry  *rte;
+	RelOptInfo	   *baserel;
+	char			relkind;
+	ListCell	   *lc;
+	List		   *ctid_quals = NIL;
+
+	if (cscan_arg->cpp_class != CUSTOMPLAN_CLASS_SCAN)
+		PG_RETURN_VOID();
+
+	root = cscan_arg->root;
+	rte = cscan_arg->rte;
+	baserel = cscan_arg->baserel;
+
+	/* all we can support is regular relations */
+	if (rte->rtekind != RTE_RELATION)
+		PG_RETURN_VOID();
+
+	relkind = get_rel_relkind(rte->relid);
+	if (relkind != RELKIND_RELATION &&
+		relkind != RELKIND_MATVIEW &&
+		relkind != RELKIND_TOASTVALUE)
+		PG_RETURN_VOID();
+
+	/* 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_CustomPlan;
+		ctid_path->cpath.path.parent = baserel;
+		ctid_path->cpath.path.param_info
+			= get_baserel_parampathinfo(root, baserel, required_outer);
+		ctid_path->cpath.methods = &ctidscan_path_methods;
+		ctid_path->cpath.flags = CPPFLAG_SUPPORT_BACKWARD_SCAN;
+		ctid_path->ctid_quals = ctid_quals;
+
+		CTidEstimateCosts(root, baserel, ctid_path);
+
+		add_path(baserel, &ctid_path->cpath.path);
+	}
+	PG_RETURN_VOID();
+}
+PG_FUNCTION_INFO_V1(CtidScanAddPath);
+
+/*
+ * Entrypoint of this extension
+ */
+void
+_PG_init(void)
+{
+	/* setup ctidscan_path_methods */
+	ctidscan_path_methods.CustomName = "ctidscan";
+	ctidscan_path_methods.CreateCustomPlan = CreateCtidScanPlan;
+	ctidscan_path_methods.TextOutCustomPath = TextOutCtidScanPath;
+
+	/* setup ctidscan_plan_methods */
+	ctidscan_plan_methods.CustomName = "ctidscan";
+	ctidscan_plan_methods.InitCustomPlan = InitCtidScanPlan;
+	ctidscan_plan_methods.SetCustomPlanRef = SetCtidScanPlanRef;
+	ctidscan_plan_methods.FinalizeCustomPlan = FinalizeCtidScanPlan;
+	ctidscan_plan_methods.CreateCustomPlanState = CreateCtidScanState;
+	ctidscan_plan_methods.TextOutCustomPlan = TextOutCtidScanPlan;
+	ctidscan_plan_methods.CopyCustomPlan = CopyCtidScanPlan;
+
+	/* setup ctidscan_planstate_methods */
+	ctidscan_exec_methods.CustomName = "ctidscan";
+	ctidscan_exec_methods.BeginCustomPlan = BeginCtidScan;
+	ctidscan_exec_methods.ExecCustomPlan = ExecCtidScan;
+	ctidscan_exec_methods.EndCustomPlan = EndCtidScan;
+	ctidscan_exec_methods.ReScanCustomPlan = ReScanCtidScan;
+	ctidscan_exec_methods.MarkPosCustomPlan = NULL;
+	ctidscan_exec_methods.RestrPosCustomPlan = NULL;
+	ctidscan_exec_methods.ExplainCustomPlanTargetRel = ExplanCtidScanTargetRel;
+	ctidscan_exec_methods.ExplainCustomPlan = ExplainCtidScan;
+	ctidscan_exec_methods.ExplainCustomPreScanNode = ExplainCtidPreScanNode;
+	ctidscan_exec_methods.GetSpecialCustomVar = NULL;
+}
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..74d66e6
--- /dev/null
+++ b/contrib/ctidscan/expected/ctidscan.out
@@ -0,0 +1,312 @@
+--
+-- Regression Tests for Custom Plan APIs
+--
+CREATE EXTENSION ctidscan;
+-- 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                              
+----------------------------------------------------------------------
+ Custom (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 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 (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 (ctidscan) on t1
+               Filter: (ctid < '(2,10)'::tid)
+               ctid quals: (ctid < '(2,10)'::tid)
+   ->  Sort
+         Sort Key: t2.ctid
+         ->  Custom (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 (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 (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)
+
+-- 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..213a97a
--- /dev/null
+++ b/contrib/ctidscan/sql/ctidscan.sql
@@ -0,0 +1,54 @@
+--
+-- Regression Tests for Custom Plan APIs
+--
+
+CREATE EXTENSION ctidscan;
+
+-- 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;
+
+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);
+
+-- Test cleanup
+DROP SCHEMA regtest_custom_scan CASCADE;
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index b4a06e4..3d7a289 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -109,6 +109,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-custom-plan-provider"><structname>pg_custom_plan_provider</structname></link></entry>
+      <entry>custom plan providers</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-conversion"><structname>pg_conversion</structname></link></entry>
       <entry>encoding conversion information</entry>
      </row>
@@ -2508,6 +2513,60 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-custom-plan-provider">
+  <title><structname>pg_custom_plan_provider</structname></title>
+
+  <indexterm zone="catalog-pg-custom-plan-provider">
+   <primary>pg_custom_plan_provider</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_custom_plan_provider</structname> describes
+   custom-plan providers. See <xref linkend="sql-createcustomplanprovider">
+   for more information.
+  </para>
+
+  <table>
+   <title><structname>pg_custom_plan_provider</> Columns</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>References</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><structfield>oid</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry></entry>
+      <entry>Row identifier (hidden attribute; must be explicitly selected)</entry>
+     </row>
+     <row>
+      <entry><structfield>cppname</structfield></entry>
+      <entry><type>name</type></entry>
+      <entry></entry>
+      <entry>custom-plan provider name</entry>
+     </row>
+     <row>
+      <entry><structfield>cppclass</structfield></entry>
+      <entry><type>char</type></entry>
+      <entry></entry>
+      <entry>class of custom-plan node on which this custom-plan provider can perform</entry>
+     </row>
+     <row>
+      <entry><structfield>cpphandler</structfield></entry>
+      <entry><type>regproc</type></entry>
+      <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
+      <entry>handler function of custom-plan provder that will propose an alternative query execution path</entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
  <sect1 id="catalog-pg-database">
   <title><structname>pg_database</structname></title>
 
diff --git a/doc/src/sgml/custom-plan-provider.sgml b/doc/src/sgml/custom-plan-provider.sgml
new file mode 100644
index 0000000..1d4a9c3
--- /dev/null
+++ b/doc/src/sgml/custom-plan-provider.sgml
@@ -0,0 +1,413 @@
+<!-- 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 variable 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 the extension that provides custom-plan custom plan
+  provider.
+ </para>
+
+ <sect1 id="cpp-overall-steps">
+  <title>Custom Plan Overall Steps</title>
+  <para>
+   A custom plan provider can be registered using
+   <xref linkend="sql-createcustomplanprovider"> command.
+   It takes a class of custom plan and its handler function.
+   Class of custom plan specifies the task to be replaced by the custom-
+   plan being defined.
+   Only <literal>SCAN</> is the supported class of custom-plan right now.
+   Custom plan handler function is an entrypoint to the planner. It calls
+   the handler function during construction of query execution path.
+  </para>
+  <para>
+   Handler function has to be declared as a function that takes one
+   <literal>internal</> data type and returns <literal>void</>.
+   On invocation, query planner delivers a pointer of data structure
+   according to the custom plan class, custom-plan handler function will
+   decide whether it can offer alternative execution path towards the
+   required task. If available, it can add a <literal>CustomPath</>
+   (or inherited object type) as one of the candidate execution path
+   with estimated cost and set of callbacks defined in the
+   <literal>CustomPathMethods</> structure.
+  </para>
+  <para>
+   Planner compares all the potential execution paths based on its cost.
+   Once a custom path that was added by custom plan provider gets chosen,
+   <literal>CreateCustomPlan</> callback shall be called, to populate
+   a <literal>CustomPlan</> (or inherited object type) node according to
+   the <literal>CustomPath</> node preliminary constructed.
+   In the similar manner, <literal>CreateCustomPlanState</> callback shall
+   be called, to populate a <literal>CustomPlanState</> (or inherited
+   object type) node according to the <literal>CustomPlan</> node being
+   constructed above.
+   The reason why custom-plan provider has to allocate a node object by
+   itself is that we allow to extend the base types to store private
+   fields managed by individual custom-plan providers, thus only custom-
+   plan provider knows actual data size to be allocated.
+  </para>
+  <para>
+   Once a <literal>CustomPlanState</> is constructed, its callback
+   functions are invoked by executor, so custom-plan provider performs
+   required tasks then pops up the result.
+  </para>
+ </sect1>
+
+ <sect1 id="cpp-handler-function">
+  <title>Custom Plan Handler Functions</title>
+  <para>
+   A handler function that was specified on
+   the <xref linkend="sql-createcustomplanprovider"> command is
+   declared to return <literal>void</> data type and takes an
+   <literal>internal</> data type; that is usually applied to exchange
+   internal data structure.
+   This handler function is not an exception. It can reference a pointer
+   being informed via function argument to understand the context.
+  </para>
+  <para>
+   The data structure of arguments fully depend on the class of custom-
+   plan. In case of <literal>SCAN</> class (that is only supported one
+   right now), the first argument points the following data structure
+   that has enough information to construct a <literal>Path</> node.
+<programlisting>
+typedef struct {
+    uint32          cpp_class;
+    PlannerInfo    *root;
+    RelOptInfo     *baserel;
+    RangeTblEntry  *rte;
+} customScanArg;
+</programlisting>
+   <literal>cpp_class</>is always <literal>CUSTOMPLAN_CLASS_SCAN</>
+   that shows this structure is for scan class.
+   <literal>root</> is <literal>PlannerInfo</> of this plan,
+   <literal>baserel</> is <literal>RelOptInfo</> of the relation to
+   be scanned, and <literal>rte</> is <literal>RangeTblEntry</> of
+   the relation.
+  </para>
+  <para>
+   The custom-plan provider being invoked can check whether it can
+   provides alternative way to scan the relation. If available, it
+   shall construct <literal>CustomPath</> or its inherited one with
+   estimated cost and callbacks below, then register the path using
+   <literal>add_path</> towards the supplied <literal>RelOptInfo</>.
+  </para>
+ </sect1>
+
+ <sect1 id="cpp-path-callbacks">
+  <title>Custom Path Callbacks</title>
+  <para>
+   This section introduces callback functions of <literal>CustomPath</>
+   structure; defined in the <literal>CustomPathMethods</>.
+  </para>
+  <para>
+<programlisting>
+Node *
+CreateCustomPlan(PlannerInfo *root,
+                 CustomPath *best_path);
+</programlisting>
+   It populates a <literal>CustomPlan</> (or inherited data type) node
+   according to the supplied <literal>CustomPath</> node which was
+   constructed on the custom plan handler function then chosen by the
+   query planner.
+   Only custom plan provider can know exact size of the node to be
+   allocated, this callback allocate a <literal>CustomPlan</> node with
+   <literal>CustomPlanMethods</> callbacks table 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 custom-plan
+   provider extends <literal>CustomPath</> data type, it shall to put private
+   fields on the supplied <literal>StringInfo</> with text form.
+   Note that common fields in <literal>CustomPath</> are handled by backend,
+   so extension needs to do nothing special.
+  </para>
+ </sect1>
+
+ <sect1 id="cpp-plan-callbacks">
+  <title>Custom Plan Callbacks</title>
+  <para>
+   This section introduces callback functions of <literal>CustomPlan</>
+   structure; defined in the <literal>CustomPlanMethods</>.
+  </para>
+  <para>
+<programlisting>
+Plan      *
+InitCustomPlan(CustomPlan *custom_plan,
+               PlannerInfo *root,
+               CustomPath *best_path,
+               List *tlist,
+               List *clauses);
+</programlisting>
+   It initializes the <literal>CustomPlan</> node being acquired by
+   <literal>CreateCustomPlan</> callback.
+   The backend takes some common initializations prior to its invocation.
+   <literal>tlist</> and <literal>clauses</> are extracted from the path
+   node according to the usual manner, so all custom plan provider has to
+   do is putting these members if nothing special are done.
+  </para>
+
+  <para>
+<programlisting>
+void
+SetCustomPlanRef(PlannerInfo *root,
+                 CustomPlan *custom_plan,
+                 int rtoffset);
+</programlisting>
+   It adjusts <literal>varno</> and <literal>varattno</> of var-nodes in
+   the expression tree chained from <literal>CustomPlan</> 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 <literal>fix_scan_expr</> shall be called on expression nodes to
+   track plan dependency.
+  </para>
+
+  <para>
+<programlisting>
+void
+FinalizeCustomPlan(PlannerInfo *root,
+                   CustomPlan *custom_plan,
+                   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 custom-plan 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 custom-plan 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 *
+CreateCustomPlanState(CustomPlan *custom_plan);
+</programlisting>
+   It populates a <literal>CustomPlanState</> (or inherited data type)
+   node according to the supplied <literal>CustomPlan</> node preliminary
+   constructed, on the beginning of query executor.
+   Only custom plan provider can know exact size of the node to be
+   allocated, this callback allocate a <literal>CustomPlanState</> node
+   with <literal>CustomExecMethods</> callbacks table and arbitrary
+   private fields.
+  </para>
+  <para>
+   Note that main purpose of this callback is allocation of
+   <literal>CustomPlanState</> node, not initialization of individual
+   fields because it shall be handled on the <literal>BeginCustomPlan</>
+   callback to be invoked next to the common usual initialization.
+  </para>
+
+  <para>
+<programlisting>
+void
+TextOutCustomPlan(StringInfo str,
+                  const CustomPlan *node);
+</programlisting>
+   It makes a text representation of custom plan node. If custom-plan
+   provider extends <literal>CustomPlan</> data type, it shall put private
+   fields on the supplied <literal>StringInfo</> with text form.
+   Note that common fields in <literal>CustomPlan</> are handled by backend,
+   so extension needs to do nothing special.
+  </para>
+
+  <para>
+<programlisting>
+CustomPlan *
+CopyCustomPlan(const CustomPlan *from);
+</programlisting>
+   It duplicate a <literal>CustomPlan</> node onto a newly allocated one.
+   If custom-plan provider extends the <literal>CustomPlan</> node, it shall
+   copy the private fields and callback table but no need to copy the common
+   <literal>scan</> field in the <literal>CustomPlan</> data type.
+  </para>
+ </sect1>
+
+ <sect1 id="cpp-exec-callbacks">
+  <title>Custom Executor Callbacks</title>
+  <para>
+   This section introduces callback functions of <literal>CustomPlanState</>
+   structure; defined in the <literal>CustomExecMethods</>.
+  </para>
+  <para>
+<programlisting>
+void
+BeginCustomPlan(CustomPlanState *node,
+                EState *estate,
+                int eflags);
+</programlisting>
+   It begins execution of custom-plan. This callback is invoked during
+   executor startup to initialize the supplied <literal>CustomPlanState</>
+   that was constructed on the <literal>CreateCustomPlanState</> above.
+   The custom-plan provider shall have initialization of its private fields
+   and common fields within <literal>CustomPlanState</> if needed, because
+   backend code already initializes expressions on its <literal>targetlist</>
+   and <literal>qual</>, assigns result slot according to the
+   <literal>targetlist</> and also assigns scan slot if <literal>scanrelid</>
+   is valid.
+  </para>
+
+  <para>
+<programlisting>
+TupleTableSlot *
+ExecCustomPlan(CustomPlanState *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
+   <literal>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 <literal>EState</>, to acquire per-scan
+   duration memory.
+  </para>
+
+  <para>
+<programlisting>
+Node *
+MultiExecCustomPlan(CustomPlanState *node);
+</programlisting>
+   It allows to return arbitrary data structure to the upper node, unlike
+   usual <literal>ExecCustomPlan</>. Built-in code has no invocation path
+   to call <literal>MultiExecProcNode</> towards <literal>CustomPlanState</>
+   node, so it is invoked only when a particular custom-plan provider made
+   a stacked custom-plan nodes and called <literal>MultiExecProcNode</> to
+   this underlying node.
+  </para>
+  <para>
+   Note that it is custom-plan provider's responsibility to translate the
+   arbitrary data structure into <productname>PostgreSQL</>'s complianced
+   data structure when top-level <literal>CustomPlanState</> returns a row
+   using <literal>ExecCustomPlan</>.
+  </para>
+
+  <para>
+<programlisting>
+void
+EndCustomPlan(CustomPlanState *node);
+</programlisting>
+   It ends the execution of custom plan and release any resources held by
+   this node. If custom-plan provider acquired resources that is not
+   released automatically at end of executor, it is responsibility of the
+   custom plan provider.
+  </para>
+
+  <para>
+<programlisting>
+void
+ReScanCustomPlan(CustomPlanState *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 *
+MarkPosCustomPlan(CustomPlanState *node);
+</programlisting>
+   It is an optional callback, to save the current position on somewhere
+   in the private field of <literal>CustomPlanState</>, to restore the
+   position later.
+   If this method is valid, <literal>RestrPosCustomPlan</> also has to
+   be valid.
+  </para>
+
+  <para>
+<programlisting>
+void *
+RestrPosCustomPlan(CustomPlanState *node);
+</programlisting>
+   It is an optional callback, to restore the previous position being saved
+   by <literal>MarkPosCustomPlan</>.
+   If this method is valid, <literal>MarkPosCustomPlan</> also has to
+   be valid.
+  </para>
+
+  <para>
+<programlisting>
+void
+ExplainCustomPlanTargetRel(CustomPlanState *node,
+                           ExplainState *es);
+</programlisting>
+   It is an optional callback, to show the target relation to be scanned.
+   In most cases, custom plan provider put text representation of the relation
+   to be scanned according to the manner in <literal>ExplainTargetRel</>.
+  </para>
+
+  <para>
+<programlisting>
+void
+ExplainCustomPlan(CustomPlanState *node,
+                  List *ancestors,
+                  ExplainState *es);
+</programlisting>
+   It is an optional callback, to show custom-plan specific explain output.
+  </para>
+
+  <para>
+<programlisting>
+void
+ExplainCustomPreScanNode(CustomPlanState *node,
+                         Bitmapset **rels_used);
+</programlisting>
+   It is an optional callback, to inform the backend which relation is
+   referenced. It shall set <literal>scanrelid</> of the target relation.
+   If <literal>NULL</>, it means this custom-plan provider never
+   references base relations.
+  </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>CustomPlanState</> when <literal>EXPLAIN</> needs the
+   text form of the column actually referenced.
+   In case when custom-plan provider adjusted <literal>varno</> of varnodes
+   on the expression tree to use special varnos (<literal>INNER_VAR</>,
+   <literal>OUTER_VAR</> or <literal>INDEX_VAR</>), custom-plan 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 <literal>PlanState</> that shall be
+   set on the <literal>child_ps</> argument.
+  </para>
+ </sect1>
+</chapter>
+
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 5902f97..a9b9efc 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-provider SYSTEM "custom-plan-provider.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..4702178 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -242,6 +242,7 @@
   &nls;
   &plhandler;
   &fdwhandler;
+  &custom-plan-provider;
   &geqo;
   &indexam;
   &gist;
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index b685e16..ab35742 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -55,6 +55,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY createCast         SYSTEM "create_cast.sgml">
 <!ENTITY createCollation    SYSTEM "create_collation.sgml">
 <!ENTITY createConversion   SYSTEM "create_conversion.sgml">
+<!ENTITY createCustomPlanProvider SYSTEM "create_custom_plan_provider.sgml">
 <!ENTITY createDatabase     SYSTEM "create_database.sgml">
 <!ENTITY createDomain       SYSTEM "create_domain.sgml">
 <!ENTITY createEventTrigger SYSTEM "create_event_trigger.sgml">
@@ -95,6 +96,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY dropCast           SYSTEM "drop_cast.sgml">
 <!ENTITY dropCollation      SYSTEM "drop_collation.sgml">
 <!ENTITY dropConversion     SYSTEM "drop_conversion.sgml">
+<!ENTITY dropCustomPlanProvider SYSTEM "drop_custom_plan_provider.sgml">
 <!ENTITY dropDatabase       SYSTEM "drop_database.sgml">
 <!ENTITY dropDomain         SYSTEM "drop_domain.sgml">
 <!ENTITY dropEventTrigger   SYSTEM "drop_event_trigger.sgml">
diff --git a/doc/src/sgml/ref/create_custom_plan_provider.sgml b/doc/src/sgml/ref/create_custom_plan_provider.sgml
new file mode 100644
index 0000000..0816584
--- /dev/null
+++ b/doc/src/sgml/ref/create_custom_plan_provider.sgml
@@ -0,0 +1,139 @@
+<!--
+doc/src/sgml/ref/create_custom_plan_provider.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-CREATECUSTOMPLANPROVIDER">
+ <indexterm zone="sql-createcustomplanprovider">
+  <primary>CREATE CUSTOM PLAN PROVIDER</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>CREATE CUSTOM PLAN PROVIDER</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>CREATE CUSTOM PLAN PROVIDER</refname>
+  <refpurpose>define a new custom plan provider</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+CREATE CUSTOM PLAN PROVIDER <replaceable class="parameter">cpp_name</replaceable> FOR <replaceable class="parameter">cpp_class</replaceable>
+    HANDLER <replaceable class="parameter">handler_function</replaceable>
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>CREATE CUSTOM PLAN PROVIDER</command> defines a new custom-plan
+   provider.
+   The user who defines the custom-plan provider has to be a superuser.
+  </para>
+
+  <para>
+   A custom-plan provider can offer the query planner alternative options
+   to scan relation, or potentially join relations and so on, in addition
+   to the built-in logics. It is usually extension modules that implement
+   callbacks according to the custom-plan interface.
+  </para>
+  <para>
+   This statement defines a couple of an entrypoint of custom-plan provider
+   and its supporting workload type. Right now, <literal>scan</literal> is
+   the only class being supported; that enables to call extension's
+   callback during query execution instead of built-in routines like
+   <literal>SeqScan</literal> or <literal>IndexScan</literal> if its
+   cost estimation is enough reasonable.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">cpp_name</replaceable></term>
+    <listitem>
+     <para>
+      The name of the custom-plan provider to be created.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">cpp_class</replaceable></term>
+    <listitem>
+     <para>
+      Workload type on which custom-plan provider can perform.
+      Only <literal>SCAN</literal> is supported option right now.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">handler_function</replaceable></term>
+    <listitem>
+     <para>
+      A function to be called when query planner is finding the best path
+      to scan a relation.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+   The function that performs as a custom-plan provider shall be declared
+   to return <literal>void</> and take one argument with <literal>internal</>
+   data type.
+   The core <productname>PostgreSQL</> calls custom-plan provider function
+   with a set of information about planner's state and relation(s) to be
+   scanned, then this function shall check whether it can offer alternative
+   scan paths or not.
+   If available, it constructs a path object derived from
+   <literal>CustomPath</> structure, that contains a set of callbacks
+   including the ones to populate <literal>CustomPlan</> or
+   <literal>CustomPlanState</> object later.
+   If extension needs to save its private information in these object,
+   define a new structure that extends above data types.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+  <para>
+   Create a custom-plan provider <literal>ctidscan</> that uses the funcion
+   <literal>ctidscanaddpath</>.   
+<programlisting>
+CREATE CUSTOM PLAN PROVIDER ctidscan FOR scan HANDLER ctidscanaddpath;
+</programlisting>
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+  <para>
+   There is no <command>CREATE CUSTOM PLAN</command> command
+   in the SQL standard.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-dropcustomplanprovider"></member>
+  </simplelist>
+  <simplelist type="inline">
+   <member><xref linkend="custom-plan"></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/drop_custom_plan_provider.sgml b/doc/src/sgml/ref/drop_custom_plan_provider.sgml
new file mode 100644
index 0000000..6a305a8
--- /dev/null
+++ b/doc/src/sgml/ref/drop_custom_plan_provider.sgml
@@ -0,0 +1,108 @@
+<!--
+doc/src/sgml/ref/drop_custom_plan_provider.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-DROPCUSTOMPLANPROVIDER">
+ <indexterm zone="sql-dropcustomplanprovider">
+  <primary>DROP CUSTOM PLAN PROVIDER</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>DROP CUSTOM PLAN PROVIDER</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>DROP CUSTOM PLAN PROVIDER</refname>
+  <refpurpose>remove a custom-plan provider</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+DROP CUSTOM PLAN PROVIDER [ IF EXISTS ] <replaceable class="parameter">cpp_name</replaceable> [ CASCADE | RESTRICT ]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>DROP CUSTOM PLAN PROVIDER</command> removes an existing custom
+   plan provider. To execute this command, the current user must be superuser.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>IF EXISTS</literal></term>
+    <listitem>
+     <para>
+      Do not throw an error if the custom-plan provider does not exist.
+      A notice is issued in this case.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name of an existing custom-plan provider.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>CASCADE</literal></term>
+    <listitem>
+     <para>
+      Automatically drop objects that depend on the custom-plan provider.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>RESTRICT</literal></term>
+    <listitem>
+     <para>
+      Refuse to drop the custom-plan provider if any objects depend on it.
+      This is the default.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   Drop a custom-plan provider <literal>foo</> if it exists:
+<programlisting>
+DROP CUSTOM PLAN PROVIDER IF EXISTS foo;
+</programlisting></para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   There is no <command>DROP CUSTOM PLAN</command> command
+   in the SQL standard.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-createcustomplanprovider"></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index 6ec1263..1a3dbdd 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -83,6 +83,7 @@
    &createCast;
    &createCollation;
    &createConversion;
+   &createCustomPlanProvider;
    &createDatabase;
    &createDomain;
    &createEventTrigger;
@@ -123,6 +124,7 @@
    &dropCast;
    &dropCollation;
    &dropConversion;
+   &dropCustomPlanProvider;
    &dropDatabase;
    &dropDomain;
    &dropEventTrigger;
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index a974bd5..f7e29eb 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -39,7 +39,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
 	pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
 	pg_ts_parser.h pg_ts_template.h pg_extension.h \
 	pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
-	pg_foreign_table.h \
+	pg_foreign_table.h pg_custom_plan_provider.h \
 	pg_default_acl.h pg_seclabel.h pg_shseclabel.h pg_collation.h pg_range.h \
 	toasting.h indexing.h \
     )
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index d41ba49..496bc9a 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -30,6 +30,7 @@
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_conversion.h"
 #include "catalog/pg_conversion_fn.h"
+#include "catalog/pg_custom_plan_provider.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_default_acl.h"
 #include "catalog/pg_depend.h"
@@ -154,7 +155,8 @@ static const Oid object_classes[MAX_OCLASS] = {
 	UserMappingRelationId,		/* OCLASS_USER_MAPPING */
 	DefaultAclRelationId,		/* OCLASS_DEFACL */
 	ExtensionRelationId,		/* OCLASS_EXTENSION */
-	EventTriggerRelationId		/* OCLASS_EVENT_TRIGGER */
+	EventTriggerRelationId,		/* OCLASS_EVENT_TRIGGER */
+	CustomPlanProviderRelationId,		/* OCLASS_CPP */
 };
 
 
@@ -1249,6 +1251,10 @@ doDeletion(const ObjectAddress *object, int flags)
 			RemoveEventTriggerById(object->objectId);
 			break;
 
+		case OCLASS_CPP:
+			RemoveCustomPlanProviderById(object->objectId);
+			break;
+
 		default:
 			elog(ERROR, "unrecognized object class: %u",
 				 object->classId);
@@ -2316,6 +2322,9 @@ getObjectClass(const ObjectAddress *object)
 
 		case EventTriggerRelationId:
 			return OCLASS_EVENT_TRIGGER;
+
+		case CustomPlanProviderRelationId:
+			return OCLASS_CPP;
 	}
 
 	/* shouldn't get here */
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index c7c8f4b..2b0b0e1 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -30,6 +30,7 @@
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_conversion.h"
+#include "catalog/pg_custom_plan_provider.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_extension.h"
 #include "catalog/pg_foreign_data_wrapper.h"
@@ -152,6 +153,18 @@ static const ObjectPropertyType ObjectProperty[] =
 		true
 	},
 	{
+		CustomPlanProviderRelationId,
+		CustomPlanProviderOidIndexId,
+		CUSTOMPLANPROVIDEROID,
+		CUSTOMPLANPROVIDERNAME,
+		Anum_pg_custom_plan_provider_cppname,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		-1,
+		false,
+	},
+	{
 		DatabaseRelationId,
 		DatabaseOidIndexId,
 		DATABASEOID,
@@ -529,6 +542,7 @@ get_object_address(ObjectType objtype, List *objname, List *objargs,
 			case OBJECT_FDW:
 			case OBJECT_FOREIGN_SERVER:
 			case OBJECT_EVENT_TRIGGER:
+			case OBJECT_CPP:
 				address = get_object_address_unqualified(objtype,
 														 objname, missing_ok);
 				break;
@@ -755,6 +769,9 @@ get_object_address_unqualified(ObjectType objtype,
 			case OBJECT_EVENT_TRIGGER:
 				msg = gettext_noop("event trigger name cannot be qualified");
 				break;
+			case OBJECT_CPP:
+				msg = gettext_noop("custom plan provider name cannot be qualified");
+				break;
 			default:
 				elog(ERROR, "unrecognized objtype: %d", (int) objtype);
 				msg = NULL;		/* placate compiler */
@@ -815,6 +832,11 @@ get_object_address_unqualified(ObjectType objtype,
 			address.objectId = get_event_trigger_oid(name, missing_ok);
 			address.objectSubId = 0;
 			break;
+		case OBJECT_CPP:
+			address.classId = CustomPlanProviderRelationId;
+			address.objectId = get_custom_plan_provider_oid(name, missing_ok);
+			address.objectSubId = 0;
+			break;
 		default:
 			elog(ERROR, "unrecognized objtype: %d", (int) objtype);
 			/* placate compiler, which doesn't know elog won't return */
@@ -1295,6 +1317,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
 			break;
 		case OBJECT_TSPARSER:
 		case OBJECT_TSTEMPLATE:
+		case OBJECT_CPP:
 			/* We treat these object types as being owned by superusers */
 			if (!superuser_arg(roleid))
 				ereport(ERROR,
@@ -2166,6 +2189,24 @@ getObjectDescription(const ObjectAddress *object)
 				break;
 			}
 
+		case OCLASS_CPP:
+			{
+				Form_pg_custom_plan_provider cpp_form;
+				HeapTuple	tup;
+
+				tup = SearchSysCache1(CUSTOMPLANPROVIDEROID,
+									  ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR,
+						 "cache lookup failed for custom-plan provider %u",
+						 object->objectId);
+				cpp_form = (Form_pg_custom_plan_provider) GETSTRUCT(tup);
+				appendStringInfo(&buffer, _("custom plan provider %s"),
+								 NameStr(cpp_form->cppname));
+				ReleaseSysCache(tup);
+				break;
+			}
+
 		default:
 			appendStringInfo(&buffer, "unrecognized object %u %u %d",
 							 object->classId,
@@ -2577,6 +2618,10 @@ getObjectTypeDescription(const ObjectAddress *object)
 			appendStringInfoString(&buffer, "event trigger");
 			break;
 
+		case OCLASS_CPP:
+			appendStringInfoString(&buffer, "custom plan provider");
+			break;
+
 		default:
 			appendStringInfo(&buffer, "unrecognized %u", object->classId);
 			break;
@@ -3330,6 +3375,24 @@ getObjectIdentity(const ObjectAddress *object)
 				break;
 			}
 
+		case OCLASS_CPP:
+			{
+				HeapTuple	tup;
+				Form_pg_custom_plan_provider cpp_form;
+
+				tup = SearchSysCache1(CUSTOMPLANPROVIDEROID,
+									  ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR,
+						 "cache lookup failed for custom-plan provider %u",
+						 object->objectId);
+				cpp_form = (Form_pg_custom_plan_provider) GETSTRUCT(tup);
+				appendStringInfoString(&buffer,
+						 quote_identifier(NameStr(cpp_form->cppname)));
+				ReleaseSysCache(tup);
+				break;
+			}
+
 		default:
 			appendStringInfo(&buffer, "unrecognized object %u %u %d",
 							 object->classId,
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index 22f116b..1e8e6f4 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -14,7 +14,7 @@ include $(top_builddir)/src/Makefile.global
 
 OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o  \
 	collationcmds.o constraint.o conversioncmds.o copy.o createas.o \
-	dbcommands.o define.o discard.o dropcmds.o \
+	custom_plan.o dbcommands.o define.o discard.o dropcmds.o \
 	event_trigger.o explain.o extension.o foreigncmds.o functioncmds.o \
 	indexcmds.o lockcmds.o matview.o operatorcmds.o opclasscmds.o \
 	portalcmds.o prepare.o proclang.o \
diff --git a/src/backend/commands/custom_plan.c b/src/backend/commands/custom_plan.c
new file mode 100644
index 0000000..2a81a7b
--- /dev/null
+++ b/src/backend/commands/custom_plan.c
@@ -0,0 +1,188 @@
+/*-------------------------------------------------------------------------
+ *
+ * custom_plan.c
+ *		custom plan nodes creation/manipulation commands
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ *    src/backend/commands/custom_plan.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_custom_plan_provider.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+#include "commands/defrem.h"
+#include "fmgr.h"
+#include "miscadmin.h"
+#include "parser/parse_func.h"
+#include "utils/builtins.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+/*
+ * utility function to lookup a custom-plan provider by name
+ */
+Oid
+get_custom_plan_provider_oid(const char *cpp_name, bool missing_ok)
+{
+	Oid		cpp_oid;
+
+	cpp_oid = GetSysCacheOid1(CUSTOMPLANPROVIDERNAME,
+							   CStringGetDatum(cpp_name));
+	if (!OidIsValid(cpp_oid) && !missing_ok)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("custom-plan provider \"%s\" does not exist",
+						cpp_name)));
+	return cpp_oid;
+}
+
+/*
+ * Drop a custom-plan provider
+ */
+void
+RemoveCustomPlanProviderById(Oid cpp_oid)
+{
+	Relation	rel;
+	HeapTuple	tuple;
+
+	rel = heap_open(CustomPlanProviderRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCache1(CUSTOMPLANPROVIDEROID,
+							ObjectIdGetDatum(cpp_oid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for custom-plan provider %u",
+			 cpp_oid);
+
+	simple_heap_delete(rel, &tuple->t_self);
+
+	ReleaseSysCache(tuple);
+
+	heap_close(rel, RowExclusiveLock);
+}
+
+/*
+ * Create a custom-plan provider
+ */
+Oid
+DefineCustomPlanProvider(CreateCustomPlanProviderStmt *stmt)
+{
+	Relation	rel;
+	Oid			cpp_oid;
+	Oid			cpp_handler = InvalidOid;
+	Datum		values[Natts_pg_custom_plan_provider];
+	bool		isnull[Natts_pg_custom_plan_provider];
+	HeapTuple	tuple;
+	ListCell   *cell;
+	ObjectAddress myself;
+	ObjectAddress referenced;
+
+	rel = heap_open(CustomPlanProviderRelationId, RowExclusiveLock);
+
+	/* must be super user */
+	if (!superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+			 errmsg("permission denied to create custom-plan provider \"%s\"",
+					stmt->cpp_name),
+			 errhint("Must be superuser to create a custom-plan node.")));
+
+	/* check namespace conflicts */
+	cpp_oid = get_custom_plan_provider_oid(stmt->cpp_name, true);
+	if (OidIsValid(cpp_oid))
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("custom-plan provider \"%s\" already exists",
+						stmt->cpp_name)));
+
+	/* check custom-plan class */
+	if (stmt->cpp_class != CUSTOMPLAN_CLASS_SCAN)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("unexpected custom plan class specified: %d",
+						(int)stmt->cpp_class)));
+
+	/* parse custom-plan options */
+	foreach (cell, stmt->cpp_options)
+	{
+		DefElem	   *defel = lfirst(cell);
+
+		Assert(IsA(defel, DefElem));
+
+		if (strcmp(defel->defname, "handler") == 0)
+		{
+			Oid		argtypes[1];
+
+			if (OidIsValid(cpp_handler))
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+
+			argtypes[0] = INTERNALOID;
+			cpp_handler = LookupFuncName((List *)defel->arg,
+										 1, argtypes, false);
+			if (get_func_rettype(cpp_handler) != VOIDOID)
+				ereport(ERROR,
+						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+						 errmsg("function %s must return type \"void\"",
+								NameListToString((List *) defel->arg))));
+		}
+		else
+			elog(ERROR, "unexpected custom-plan provider option: %s",
+				 defel->defname);
+	}
+
+	if (!OidIsValid(cpp_handler))
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("HANDLER must be provided")));
+
+	/*
+	 * Insert tuple into pg_custom_plan system catalog
+	 */
+	memset(values, 0, sizeof(values));
+	memset(isnull, 0, sizeof(isnull));
+	values[Anum_pg_custom_plan_provider_cppname - 1]
+		= DirectFunctionCall1(namein, CStringGetDatum(stmt->cpp_name));
+	values[Anum_pg_custom_plan_provider_cppclass - 1]
+		= stmt->cpp_class;
+	values[Anum_pg_custom_plan_provider_cpphandler - 1]
+		= ObjectIdGetDatum(cpp_handler);
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), values, isnull);
+
+	cpp_oid = simple_heap_insert(rel, tuple);
+	CatalogUpdateIndexes(rel, tuple);
+
+	heap_freetuple(tuple);
+
+	/* record dependencies */
+	myself.classId = CustomPlanProviderRelationId;
+	myself.objectId = cpp_oid;
+	myself.objectSubId = 0;
+
+	referenced.classId = ProcedureRelationId;
+	referenced.objectId = cpp_handler;
+	referenced.objectSubId = 0;
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+	/* Post creation hook for new custom-plan provider */
+	InvokeObjectPostCreateHook(CustomPlanProviderRelationId, cpp_oid, 0);
+
+	heap_close(rel, RowExclusiveLock);
+
+	return cpp_oid;
+}
diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c
index e64ad80..c6d4576 100644
--- a/src/backend/commands/dropcmds.c
+++ b/src/backend/commands/dropcmds.c
@@ -408,6 +408,11 @@ does_not_exist_skipping(ObjectType objtype, List *objname, List *objargs)
 				args = strVal(linitial(objargs));
 			}
 			break;
+		case OBJECT_CPP:
+			msg = gettext_noop("custom-plan provider \"%s\" does not exist, skipping");
+			name = NameListToString(objname);
+			break;
+
 		default:
 			elog(ERROR, "unexpected object type (%d)", (int) objtype);
 			break;
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 754264e..76968a2 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -923,6 +923,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
 		case OBJECT_CONSTRAINT:
 		case OBJECT_COLLATION:
 		case OBJECT_CONVERSION:
+		case OBJECT_CPP:
 		case OBJECT_DOMAIN:
 		case OBJECT_EXTENSION:
 		case OBJECT_FDW:
@@ -975,6 +976,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
 		case OCLASS_COLLATION:
 		case OCLASS_CONSTRAINT:
 		case OCLASS_CONVERSION:
+		case OCLASS_CPP:
 		case OCLASS_DEFAULT:
 		case OCLASS_LANGUAGE:
 		case OCLASS_LARGEOBJECT:
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 781a736..95e8b0e 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -722,6 +722,14 @@ ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
 			*rels_used = bms_add_member(*rels_used,
 										((Scan *) plan)->scanrelid);
 			break;
+		case T_CustomPlan:
+			{
+				CustomPlanState *cps = (CustomPlanState *)planstate;
+
+				if (cps->methods->ExplainCustomPreScanNode)
+					cps->methods->ExplainCustomPreScanNode(cps, rels_used);
+			}
+			break;
 		case T_ModifyTable:
 			/* cf ExplainModifyTarget */
 			*rels_used = bms_add_member(*rels_used,
@@ -848,6 +856,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 *cpp_name = NULL;
 	int			save_indent = es->indent;
 	bool		haschildren;
 
@@ -936,6 +945,14 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_ForeignScan:
 			pname = sname = "Foreign Scan";
 			break;
+		case T_CustomPlan:
+			sname = "Custom";
+			cpp_name = ((CustomPlan *) plan)->methods->CustomName;
+			if (cpp_name)
+				pname = psprintf("Custom (%s)", cpp_name);
+			else
+				pname = sname;
+			break;
 		case T_Material:
 			pname = sname = "Materialize";
 			break;
@@ -1037,6 +1054,8 @@ ExplainNode(PlanState *planstate, List *ancestors,
 			ExplainPropertyText("Parent Relationship", relationship, es);
 		if (plan_name)
 			ExplainPropertyText("Subplan Name", plan_name, es);
+		if (cpp_name)
+			ExplainPropertyText("Custom", cpp_name, es);
 	}
 
 	switch (nodeTag(plan))
@@ -1084,6 +1103,14 @@ ExplainNode(PlanState *planstate, List *ancestors,
 					ExplainPropertyText("Index Name", indexname, es);
 			}
 			break;
+		case T_CustomPlan:
+			{
+				CustomPlanState *cps = (CustomPlanState *)planstate;
+
+				if (cps->methods->ExplainCustomPlanTargetRel)
+					cps->methods->ExplainCustomPlanTargetRel(cps, es);
+			}
+			break;
 		case T_ModifyTable:
 			ExplainModifyTarget((ModifyTable *) plan, es);
 			break;
@@ -1353,6 +1380,18 @@ ExplainNode(PlanState *planstate, List *ancestors,
 										   planstate, es);
 			show_foreignscan_info((ForeignScanState *) planstate, es);
 			break;
+		case T_CustomPlan:
+			{
+				CustomPlanState *cps = (CustomPlanState *) planstate;
+
+				show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
+				if (plan->qual)
+					show_instrumentation_count("Rows Removed by Filter", 1,
+											   planstate, es);
+				if (cps->methods->ExplainCustomPlan)
+					cps->methods->ExplainCustomPlan(cps, 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..eaab1df 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -13,6 +13,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "catalog/pg_custom_plan_provider.h"
 #include "executor/execdebug.h"
 #include "executor/nodeAgg.h"
 #include "executor/nodeAppend.h"
@@ -21,6 +22,7 @@
 #include "executor/nodeBitmapIndexscan.h"
 #include "executor/nodeBitmapOr.h"
 #include "executor/nodeCtescan.h"
+#include "executor/nodeCustom.h"
 #include "executor/nodeForeignscan.h"
 #include "executor/nodeFunctionscan.h"
 #include "executor/nodeGroup.h"
@@ -197,6 +199,10 @@ ExecReScan(PlanState *node)
 			ExecReScanForeignScan((ForeignScanState *) node);
 			break;
 
+		case T_CustomPlanState:
+			ExecReScanCustomPlan((CustomPlanState *) node);
+			break;
+
 		case T_NestLoopState:
 			ExecReScanNestLoop((NestLoopState *) node);
 			break;
@@ -291,6 +297,10 @@ ExecMarkPos(PlanState *node)
 			ExecValuesMarkPos((ValuesScanState *) node);
 			break;
 
+		case T_CustomPlanState:
+			ExecCustomMarkPos((CustomPlanState *) node);
+			break;
+
 		case T_MaterialState:
 			ExecMaterialMarkPos((MaterialState *) node);
 			break;
@@ -348,6 +358,10 @@ ExecRestrPos(PlanState *node)
 			ExecValuesRestrPos((ValuesScanState *) node);
 			break;
 
+		case T_CustomPlanState:
+			ExecCustomRestrPos((CustomPlanState *) 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_CustomPlan:
+			{
+				CustomPath *cpath = (CustomPath *) pathnode;
+
+				Assert(IsA(cpath, CustomPath));
+				if ((cpath->flags & CPPFLAG_SUPPORT_MARK_RESTORE) != 0)
+					return true;
+			}
+			break;
+
 		default:
 			break;
 	}
@@ -465,6 +489,16 @@ ExecSupportsBackwardScan(Plan *node)
 			return ExecSupportsBackwardScan(((SubqueryScan *) node)->subplan) &&
 				TargetListSupportsBackwardScan(node->targetlist);
 
+		case T_CustomPlan:
+			{
+				CustomPlan *cplan = (CustomPlan *) node;
+
+				if ((cplan->flags & CPPFLAG_SUPPORT_BACKWARD_SCAN) != 0 &&
+					TargetListSupportsBackwardScan(node->targetlist))
+					return false;
+			}
+			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..ae77283 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_CustomPlan:
+			result = (PlanState *) ExecInitCustomPlan((CustomPlan *) node,
+													  estate, eflags);
+			break;
+
 			/*
 			 * join nodes
 			 */
@@ -442,6 +448,10 @@ ExecProcNode(PlanState *node)
 			result = ExecForeignScan((ForeignScanState *) node);
 			break;
 
+		case T_CustomPlanState:
+			result = ExecCustomPlan((CustomPlanState *) node);
+			break;
+
 			/*
 			 * join nodes
 			 */
@@ -558,6 +568,10 @@ MultiExecProcNode(PlanState *node)
 			result = MultiExecBitmapOr((BitmapOrState *) node);
 			break;
 
+		case T_CustomPlanState:
+			result = MultiExecCustomPlan((CustomPlanState *) node);
+			break;
+
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
 			result = NULL;
@@ -678,6 +692,10 @@ ExecEndNode(PlanState *node)
 			ExecEndForeignScan((ForeignScanState *) node);
 			break;
 
+		case T_CustomPlanState:
+			ExecEndCustomPlan((CustomPlanState *) node);
+			break;
+
 			/*
 			 * join nodes
 			 */
diff --git a/src/backend/executor/nodeCustom.c b/src/backend/executor/nodeCustom.c
new file mode 100644
index 0000000..785909e
--- /dev/null
+++ b/src/backend/executor/nodeCustom.c
@@ -0,0 +1,147 @@
+/* ------------------------------------------------------------------------
+ *
+ * nodeCustom.c
+ *		Routines to handle execution of custom plan 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"
+
+CustomPlanState *
+ExecInitCustomPlan(CustomPlan *cplan, EState *estate, int eflags)
+{
+	CustomPlanState    *cps;
+
+	/* populate a CustomPlanState according to the CustomPlan */
+	cps = (CustomPlanState *)cplan->methods->CreateCustomPlanState(cplan);
+	Assert(IsA(cps, CustomPlanState));
+
+	/* fill up fields of PlanState */
+	cps->ss.ps.plan = &cplan->scan.plan;
+	cps->ss.ps.state = estate;
+
+	/* create expression context for node */
+	ExecAssignExprContext(estate, &cps->ss.ps);
+	cps->ss.ps.ps_TupFromTlist = false;
+
+	/* initialize child expressions */
+	cps->ss.ps.targetlist = (List *)
+		ExecInitExpr((Expr *) cplan->scan.plan.targetlist,
+					 (PlanState *) cps);
+	cps->ss.ps.qual = (List *)
+		ExecInitExpr((Expr *) cplan->scan.plan.qual,
+					 (PlanState *) cps);
+
+	/* initialization of result tuple slot  */
+	ExecInitResultTupleSlot(estate, &cps->ss.ps);
+	ExecAssignResultTypeFromTL(&cps->ss.ps);
+
+	if (cplan->scan.scanrelid > 0)
+	{
+		Relation	heap_rel;
+
+		heap_rel = ExecOpenScanRelation(estate, cplan->scan.scanrelid, eflags);
+		cps->ss.ss_currentRelation = heap_rel;
+		cps->ss.ss_currentScanDesc = NULL;	/* set by provider on demand */
+		ExecInitScanTupleSlot(estate, &cps->ss);
+		ExecAssignScanType(&cps->ss, RelationGetDescr(heap_rel));
+		ExecAssignScanProjectionInfo(&cps->ss);
+	}
+	else
+	{
+		/*
+		 * Elsewhere, custom-plan provider should be responsible to put
+		 * appropriate initialization of scan tuple-slot and projection
+		 * info.
+		 */
+		cps->ss.ss_currentRelation = NULL;
+		cps->ss.ss_currentScanDesc = NULL;
+		cps->ss.ss_ScanTupleSlot = NULL;
+		cps->ss.ps.ps_ProjInfo = NULL;
+	}
+	/*
+	 * Then, custom-plan provider can have all the own original
+	 * initialization on demand.
+	 */
+	cps->methods->BeginCustomPlan(cps, estate, eflags);
+
+	return cps;
+}
+
+TupleTableSlot *
+ExecCustomPlan(CustomPlanState *cpstate)
+{
+	Assert(cpstate->methods->ExecCustomPlan != NULL);
+	return cpstate->methods->ExecCustomPlan(cpstate);
+}
+
+Node *
+MultiExecCustomPlan(CustomPlanState *cpstate)
+{
+	if (!cpstate->methods->MultiExecCustomPlan)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("CustomPlan \"%s\" does not support MultiExec method",
+						cpstate->methods->CustomName)));
+	return cpstate->methods->MultiExecCustomPlan(cpstate);
+}
+
+void
+ExecEndCustomPlan(CustomPlanState *cpstate)
+{
+	Assert(cpstate->methods->EndCustomPlan != NULL);
+	cpstate->methods->EndCustomPlan(cpstate);
+
+	/* Free the exprcontext */
+	ExecFreeExprContext(&cpstate->ss.ps);
+
+	/* Clean out the tuple table */
+	ExecClearTuple(cpstate->ss.ps.ps_ResultTupleSlot);
+	if (cpstate->ss.ss_ScanTupleSlot)
+		ExecClearTuple(cpstate->ss.ss_ScanTupleSlot);
+
+	/* Close the heap relation, if needed */
+	if (cpstate->ss.ss_currentRelation)
+		ExecCloseScanRelation(cpstate->ss.ss_currentRelation);
+}
+
+void
+ExecReScanCustomPlan(CustomPlanState *cpstate)
+{
+	Assert(cpstate->methods->ReScanCustomPlan != NULL);
+	cpstate->methods->ReScanCustomPlan(cpstate);
+}
+
+void
+ExecCustomMarkPos(CustomPlanState *cpstate)
+{
+	if (!cpstate->methods->MarkPosCustomPlan)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+		errmsg("MarkPos is not supported by custom plan provider: %s",
+			   cpstate->methods->CustomName)));
+	cpstate->methods->MarkPosCustomPlan(cpstate);
+}
+
+void
+ExecCustomRestrPos(CustomPlanState *cpstate)
+{
+	if (!cpstate->methods->RestrPosCustomPlan)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+		errmsg("RestrPos is not supported by custom plan provider: %s",
+			   cpstate->methods->CustomName)));
+	cpstate->methods->RestrPosCustomPlan(cpstate);
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 3088578..14a3e41 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -598,6 +598,22 @@ _copyForeignScan(const ForeignScan *from)
 }
 
 /*
+ * _copyCustomPlan
+ */
+static CustomPlan *
+_copyCustomPlan(const CustomPlan *from)
+{
+	CustomPlan *newnode;
+
+	newnode = from->methods->CopyCustomPlan(from);
+	Assert(nodeTag(newnode) == nodeTag(from));
+	CopyScanFields((const Scan *) from, (Scan *) newnode);
+	COPY_SCALAR_FIELD(flags);
+
+	return newnode;
+}
+
+/*
  * CopyJoinFields
  *
  *		This function copies the fields of the Join node.  It is used by
@@ -3849,6 +3865,19 @@ _copyAlterTSConfigurationStmt(const AlterTSConfigurationStmt *from)
 	return newnode;
 }
 
+static CreateCustomPlanProviderStmt *
+_copyCreateCustomPlanProviderStmt(const CreateCustomPlanProviderStmt *from)
+{
+	CreateCustomPlanProviderStmt *newnode
+		= makeNode(CreateCustomPlanProviderStmt);
+
+	COPY_STRING_FIELD(cpp_name);
+	COPY_SCALAR_FIELD(cpp_class);
+	COPY_NODE_FIELD(cpp_options);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					pg_list.h copy functions
  * ****************************************************************
@@ -4012,6 +4041,9 @@ copyObject(const void *from)
 		case T_ForeignScan:
 			retval = _copyForeignScan(from);
 			break;
+		case T_CustomPlan:
+			retval = _copyCustomPlan(from);
+			break;
 		case T_Join:
 			retval = _copyJoin(from);
 			break;
@@ -4561,6 +4593,9 @@ copyObject(const void *from)
 		case T_AlterTSConfigurationStmt:
 			retval = _copyAlterTSConfigurationStmt(from);
 			break;
+		case T_CreateCustomPlanProviderStmt:
+			retval = _copyCreateCustomPlanProviderStmt(from);
+			break;
 
 		case T_A_Expr:
 			retval = _copyAExpr(from);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 1b07db6..bdd626e 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2008,6 +2008,17 @@ _equalAlterTSConfigurationStmt(const AlterTSConfigurationStmt *a,
 }
 
 static bool
+_equalCreateCustomPlanProviderStmt(const CreateCustomPlanProviderStmt *a,
+								   const CreateCustomPlanProviderStmt *b)
+{
+	COMPARE_STRING_FIELD(cpp_name);
+	COMPARE_SCALAR_FIELD(cpp_class);
+	COMPARE_NODE_FIELD(cpp_options);
+
+	return true;
+}
+
+static bool
 _equalAExpr(const A_Expr *a, const A_Expr *b)
 {
 	COMPARE_SCALAR_FIELD(kind);
@@ -3025,6 +3036,9 @@ equal(const void *a, const void *b)
 		case T_AlterTSConfigurationStmt:
 			retval = _equalAlterTSConfigurationStmt(a, b);
 			break;
+		case T_CreateCustomPlanProviderStmt:
+			retval = _equalCreateCustomPlanProviderStmt(a, b);
+			break;
 
 		case T_A_Expr:
 			retval = _equalAExpr(a, b);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 9573a9b..e014a78 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -564,6 +564,17 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
 }
 
 static void
+_outCustomPlan(StringInfo str, const CustomPlan *node)
+{
+	WRITE_NODE_TYPE("CUSTOMPLAN");
+
+	_outScanInfo(str, (const Scan *) node);
+	appendStringInfo(str, " :methods");
+	_outToken(str, node->methods->CustomName);
+	node->methods->TextOutCustomPlan(str, node);
+}
+
+static void
 _outJoin(StringInfo str, const Join *node)
 {
 	WRITE_NODE_TYPE("JOIN");
@@ -1582,6 +1593,16 @@ _outForeignPath(StringInfo str, const ForeignPath *node)
 }
 
 static void
+_outCustomPath(StringInfo str, const CustomPath *node)
+{
+	WRITE_NODE_TYPE("CUSTOMPATH");
+	_outPathInfo(str, (const Path *) node);
+	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");
@@ -2845,6 +2866,9 @@ _outNode(StringInfo str, const void *obj)
 			case T_ForeignScan:
 				_outForeignScan(str, obj);
 				break;
+			case T_CustomPlan:
+				_outCustomPlan(str, obj);
+				break;
 			case T_Join:
 				_outJoin(str, obj);
 				break;
@@ -3053,6 +3077,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..dbba5ae 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -336,7 +336,7 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 				}
 				break;
 			case RTE_SUBQUERY:
-				/* Subquery --- fully handled during set_rel_size */
+				/* Subquery --- path was added during set_rel_size */
 				break;
 			case RTE_FUNCTION:
 				/* RangeFunction */
@@ -347,12 +347,17 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 				set_values_pathlist(root, rel, rte);
 				break;
 			case RTE_CTE:
-				/* CTE reference --- fully handled during set_rel_size */
+				/* CTE reference --- path was added during set_rel_size */
 				break;
 			default:
 				elog(ERROR, "unexpected rtekind: %d", (int) rel->rtekind);
 				break;
 		}
+		/* Also, consider paths by custom-plan providers */
+		call_custom_scan_providers(root, rel, rte);
+
+		/* Select cheapest path */
+		set_cheapest(rel);
 	}
 
 #ifdef OPTIMIZER_DEBUG
@@ -401,9 +406,6 @@ set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 
 	/* Consider TID scans */
 	create_tidscan_paths(root, rel);
-
-	/* Now find the cheapest of the paths for this rel */
-	set_cheapest(rel);
 }
 
 /*
@@ -429,9 +431,6 @@ set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 {
 	/* Call the FDW's GetForeignPaths function to generate path(s) */
 	rel->fdwroutine->GetForeignPaths(root, rel, rte->relid);
-
-	/* Select cheapest path */
-	set_cheapest(rel);
 }
 
 /*
@@ -1272,9 +1271,6 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
 
 	/* Generate appropriate path */
 	add_path(rel, create_subqueryscan_path(root, rel, pathkeys, required_outer));
-
-	/* Select cheapest path (pretty easy in this case...) */
-	set_cheapest(rel);
 }
 
 /*
@@ -1343,9 +1339,6 @@ set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 	/* Generate appropriate path */
 	add_path(rel, create_functionscan_path(root, rel,
 										   pathkeys, required_outer));
-
-	/* Select cheapest path (pretty easy in this case...) */
-	set_cheapest(rel);
 }
 
 /*
@@ -1366,9 +1359,6 @@ set_values_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 
 	/* Generate appropriate path */
 	add_path(rel, create_valuesscan_path(root, rel, required_outer));
-
-	/* Select cheapest path (pretty easy in this case...) */
-	set_cheapest(rel);
 }
 
 /*
@@ -1435,9 +1425,6 @@ set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 
 	/* Generate appropriate path */
 	add_path(rel, create_ctescan_path(root, rel, required_outer));
-
-	/* Select cheapest path (pretty easy in this case...) */
-	set_cheapest(rel);
 }
 
 /*
@@ -1488,9 +1475,6 @@ set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 
 	/* Generate appropriate path */
 	add_path(rel, create_worktablescan_path(root, rel, required_outer));
-
-	/* Select cheapest path (pretty easy in this case...) */
-	set_cheapest(rel);
 }
 
 /*
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 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 4b641a2..83efba4 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -77,13 +77,13 @@ 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 Plan *create_custom_plan(PlannerInfo *root, CustomPath *best_path);
 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,
 					  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);
@@ -261,6 +261,9 @@ create_plan_recurse(PlannerInfo *root, Path *best_path)
 			plan = create_unique_plan(root,
 									  (UniquePath *) best_path);
 			break;
+		case T_CustomPlan:
+			plan = create_custom_plan(root, (CustomPath *) best_path);
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) best_path->pathtype);
@@ -1072,6 +1075,96 @@ 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 'scan_clauses' and targetlist 'tlist'.
+ */
+static Plan *
+create_custom_plan(PlannerInfo *root, CustomPath *best_path)
+{
+	CustomPlan	   *custom_plan;
+	RelOptInfo	   *rel;
+	List		   *tlist = NIL;
+	List		   *clauses = NIL;
+
+	/*
+	 * Create a custom-plan object delivered from CustomPlan type,
+	 * according to the supplied CustomPath
+	 */
+	Assert(best_path->path.pathtype == T_CustomPlan);
+	custom_plan = (CustomPlan *)
+		best_path->methods->CreateCustomPlan(root, best_path);
+	Assert(IsA(custom_plan, CustomPlan));
+
+	rel = best_path->path.parent;
+	if (rel)
+	{
+		if (rel->reloptkind == RELOPT_BASEREL)
+		{
+			Assert(rel->relid > 0);
+			custom_plan->scan.scanrelid = rel->relid;
+
+			/*
+			 * For table scans, rather than using the relation targetlist
+			 * (which is only those Vars actually needed by the query),
+			 * we prefer to generate a tlist containing all Vars in order.
+			 * This will allow the executor to optimize away projection of
+			 * the table tuples, if possible.
+			 */
+			if (use_physical_tlist(root, rel))
+				tlist = build_physical_tlist(root, rel);
+		}
+		/* elsewhere, we generate a tlist from the relation targetlist */
+		if (tlist == NIL)
+			tlist = build_path_tlist(root, &best_path->path);
+
+		/*
+		 * Extract the relevant restriction clauses from the parent relation.
+		 * The executor must apply all these restrictions during the scan,
+		 * except for pseudoconstants which we'll take care of below.
+		 */
+		clauses = rel->baserestrictinfo;
+
+		/*
+		 * If this is a parameterized scan, we also need to enforce all
+		 * the join clauses available from the outer relation(s).
+		 */
+		if (best_path->path.param_info)
+			clauses = list_concat(list_copy(clauses),
+								  best_path->path.param_info->ppi_clauses);
+
+		/* Sort clauses into best execution order */
+		clauses = order_qual_clauses(root, clauses);
+
+		/*
+		 * Replace outer-relation variables with nestloop params.
+		 * Note that any other clauses which is managed by extension
+		 * itself has to be handled in InitCustomPlan() method, as
+		 * built-in code doing.
+		 */
+		if (best_path->path.param_info)
+			clauses = (List *)replace_nestloop_params(root, (Node *)clauses);
+	}
+	/*
+	 * Copy cost data from Path to Plan; no need to make custom-plan
+	 * providers do this
+	 */
+	copy_path_costsize((Plan *)custom_plan, (Path *)best_path);
+
+	/*
+	 * Let the custom-plan provider perform its final initialization
+	 * of this CustomPlan (to be an inherited type, actually) node
+	 * according to its own necessity.
+	 * Note that custom-plan provider may/can replace (or stack another
+	 * one on) its own custom-plan node on demand, for example, to add
+	 * Result node to handle pseudo constant using create_gating_plan().
+	 */
+	return custom_plan->methods->InitCustomPlan(custom_plan,
+												root, best_path,
+												tlist, clauses);
+}
 
 /*****************************************************************************
  *
@@ -2540,7 +2633,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 4d717df..c416859 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,14 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 			}
 			break;
 
+		case T_CustomPlan:
+			{
+				CustomPlan *cplan = (CustomPlan *) plan;
+
+				cplan->methods->SetCustomPlanRef(root, cplan, rtoffset);
+			}
+			break;
+
 		case T_NestLoop:
 		case T_MergeJoin:
 		case T_HashJoin:
@@ -1158,7 +1165,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..9833073 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -2283,6 +2283,30 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
 			context.paramids = bms_add_members(context.paramids, scan_params);
 			break;
 
+		case T_CustomPlan:
+			{
+				CustomPlan *cplan = (CustomPlan *) plan;
+
+				/*
+				 * If this custom-plan scab a particular relation, we
+				 * adjust paramids as other scan derivered node.
+				 */
+				if (cplan->scan.scanrelid > 0)
+					context.paramids = bms_add_members(context.paramids,
+													   scan_params);
+				/*
+				 * custom plan provider is responsible to apply
+				 * finalize_primnode() on the expression node of its
+				 * private fields, but no need to apply tlist and
+				 * qual of Plan node (already done above).
+				 */
+				if (cplan->methods->FinalizeCustomPlan)
+					cplan->methods->FinalizeCustomPlan(root, cplan,
+													   finalize_primnode,
+													   (void *)&context);
+			}
+			break;
+
 		case T_ModifyTable:
 			{
 				ModifyTable *mtplan = (ModifyTable *) plan;
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index d129f8d..e946fc7 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -16,6 +16,10 @@
 
 #include <math.h>
 
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "catalog/pg_custom_plan_provider.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
@@ -25,8 +29,11 @@
 #include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
+#include "utils/inval.h"
 #include "utils/lsyscache.h"
+#include "utils/memutils.h"
 #include "utils/selfuncs.h"
+#include "utils/syscache.h"
 
 
 typedef enum
@@ -2078,3 +2085,106 @@ reparameterize_path(PlannerInfo *root, Path *path,
 	}
 	return NULL;
 }
+
+/*****************************************************************************
+ *		creation of custom-plan paths
+ *****************************************************************************/
+static List *custom_scan_callchain = NIL;
+static bool custom_plan_callchain_is_ready = false;
+static MemoryContext custom_plan_memcxt = NULL;
+
+static void
+invalidate_custom_plan_callchain(Datum arg, int cacheid, uint32 hashvalue)
+{
+	MemoryContextReset(custom_plan_memcxt);
+	custom_plan_callchain_is_ready = false;
+	custom_scan_callchain = NIL;
+}
+
+static void
+setup_custom_plan_callchain(void)
+{
+	Relation		rel;
+	SysScanDesc		scan;
+	HeapTuple		tuple;
+	MemoryContext	oldcxt;
+
+	custom_scan_callchain = NIL;
+
+	rel = heap_open(CustomPlanProviderRelationId, AccessShareLock);
+
+	/* full scan on the pg_custom_plan once */
+	scan = systable_beginscan(rel, InvalidOid, false, NULL, 0, NULL);
+
+	oldcxt = MemoryContextSwitchTo(custom_plan_memcxt);
+	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+	{
+		Form_pg_custom_plan_provider	cppForm
+			= (Form_pg_custom_plan_provider) GETSTRUCT(tuple);
+
+		if (cppForm->cppclass == CUSTOMPLAN_CLASS_SCAN)
+		{
+			custom_scan_callchain = lappend_oid(custom_scan_callchain,
+												cppForm->cpphandler);
+		}
+		else
+			elog(LOG, "Bug? custom-plan provider \"%s\" has unknown class: %c",
+				 NameStr(cppForm->cppname), cppForm->cppclass);
+	}
+	MemoryContextSwitchTo(oldcxt);
+	systable_endscan(scan);
+
+	heap_close(rel, AccessShareLock);
+
+	custom_plan_callchain_is_ready = true;
+}
+
+static void
+init_custom_plan_callchain(void)
+{
+	/* memory context to keep callchain for custom-plans */
+	custom_plan_memcxt = AllocSetContextCreate(CacheMemoryContext,
+											   "custom plan memory context",
+											   ALLOCSET_DEFAULT_MINSIZE,
+											   ALLOCSET_DEFAULT_INITSIZE,
+											   ALLOCSET_DEFAULT_MAXSIZE);
+
+	/* flush cached callchain on catalog updates */
+	CacheRegisterSyscacheCallback(CUSTOMPLANPROVIDEROID,
+								  invalidate_custom_plan_callchain,
+								  (Datum) 0);
+	/* also, initial setting up */
+	setup_custom_plan_callchain();
+}
+
+/*
+ * call_custom_scan_providers
+ *
+ * A callchain on relation scan. custom-plan provider can add alternative
+ * scan paths derived from CustomPath class.
+ */
+void
+call_custom_scan_providers(PlannerInfo *root,
+						   RelOptInfo *baserel,
+						   RangeTblEntry *rte)
+{
+	customScanArg	sarg;
+	ListCell	   *cell;
+
+	if (!custom_plan_memcxt)
+		init_custom_plan_callchain();
+	else if (!custom_plan_callchain_is_ready)
+		setup_custom_plan_callchain();
+
+	Assert(custom_plan_callchain_is_ready);
+	sarg.cpp_class = CUSTOMPLAN_CLASS_SCAN;
+	sarg.root = root;
+	sarg.baserel = baserel;
+	sarg.rte = rte;
+
+	foreach (cell, custom_scan_callchain)
+	{
+		(void) OidFunctionCall1(lfirst_oid(cell),
+								PointerGetDatum(&sarg));
+	}
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a113809..c8ea278 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -51,6 +51,7 @@
 
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/pg_custom_plan_provider.h"
 #include "catalog/pg_trigger.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -259,6 +260,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		DropOwnedStmt ReassignOwnedStmt
 		AlterTSConfigurationStmt AlterTSDictionaryStmt
 		CreateMatViewStmt RefreshMatViewStmt
+		CreateCustomPlanProviderStmt DropCustomPlanProviderStmt
 
 %type <node>	select_no_parens select_with_parens select_clause
 				simple_select values_clause
@@ -514,6 +516,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
 
+%type <ival>	cpp_class
+%type <defelt>	cpp_option
+%type <list>	opt_cpp_options cpp_options
+
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -549,7 +555,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
 	CROSS CSV CURRENT_P
 	CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
-	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
+	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CUSTOM CYCLE
 
 	DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
 	DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DESC
@@ -588,8 +594,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	OBJECT_P OF OFF OFFSET OIDS ON ONLY OPERATOR OPTION OPTIONS OR
 	ORDER ORDINALITY OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER
 
-	PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POSITION
-	PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
+	PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLAN PLANS POSITION
+	PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY PROVIDER
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROGRAM
 
 	QUOTE
@@ -599,8 +605,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK
 	ROW ROWS RULE
 
-	SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
-	SERIALIZABLE SERVER SESSION SESSION_USER SET SETOF SHARE
+	SAVEPOINT SCAN SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE
+	SEQUENCES SERIALIZABLE SERVER SESSION SESSION_USER SET SETOF SHARE
 	SHOW SIMILAR SIMPLE SMALLINT SNAPSHOT SOME STABLE STANDALONE_P START
 	STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBSTRING
 	SYMMETRIC SYSID SYSTEM_P
@@ -762,6 +768,7 @@ stmt :
 			| CreateAssertStmt
 			| CreateCastStmt
 			| CreateConversionStmt
+			| CreateCustomPlanProviderStmt
 			| CreateDomainStmt
 			| CreateExtensionStmt
 			| CreateFdwStmt
@@ -792,6 +799,7 @@ stmt :
 			| DoStmt
 			| DropAssertStmt
 			| DropCastStmt
+			| DropCustomPlanProviderStmt
 			| DropFdwStmt
 			| DropForeignServerStmt
 			| DropGroupStmt
@@ -8705,6 +8713,78 @@ CreateConversionStmt:
 			}
 		;
 
+/****************************************************************************
+ *
+ *	QUERY:
+ *			CREATE CUSTOM PLAN PROVIDER name FOR <class> <options>
+ *
+ ****************************************************************************/
+
+CreateCustomPlanProviderStmt:
+			CREATE CUSTOM PLAN PROVIDER name FOR cpp_class opt_cpp_options
+			{
+				CreateCustomPlanProviderStmt *n
+					= makeNode(CreateCustomPlanProviderStmt);
+				n->cpp_name = $5;
+				n->cpp_class = $7;
+				n->cpp_options = $8;
+				$$ = (Node *) n;
+			}
+		;
+
+cpp_class:
+			SCAN				{ $$ = CUSTOMPLAN_CLASS_SCAN; }
+		;
+
+cpp_option:
+			HANDLER handler_name
+			{
+				$$ = makeDefElem("handler", (Node *)$2);
+			}
+		;
+
+cpp_options:
+			cpp_option					{ $$ = list_make1($1); }
+			| cpp_options cpp_option	{ $$ = lappend($1, $2); }
+		;
+
+opt_cpp_options:
+			cpp_options			{ $$ = $1; }
+			| /* empty */		{ $$ = NIL; }
+		;
+
+/****************************************************************************
+ *
+ *     QUERY:
+ *             DROP CUSTOM PLAN PROVIDER name
+ *
+ ****************************************************************************/
+
+DropCustomPlanProviderStmt:
+			DROP CUSTOM PLAN PROVIDER name opt_drop_behavior
+			{
+				DropStmt *n = makeNode(DropStmt);
+				n->removeType = OBJECT_CPP;
+				n->objects = list_make1(list_make1(makeString($5)));
+				n->arguments = NIL;
+				n->missing_ok = false;
+				n->behavior = $6;
+				n->concurrent = false;
+				$$ = (Node *) n;
+			}
+		|	DROP CUSTOM PLAN PROVIDER IF_P EXISTS name opt_drop_behavior
+			{
+				DropStmt *n = makeNode(DropStmt);
+				n->removeType = OBJECT_CPP;
+				n->objects = list_make1(list_make1(makeString($7)));
+				n->arguments = NIL;
+				n->missing_ok = true;
+				n->behavior = $8;
+				n->concurrent = false;
+				$$ = (Node *) n;
+			}
+		;
+
 /*****************************************************************************
  *
  *		QUERY:
@@ -12914,6 +12994,7 @@ unreserved_keyword:
 			| CSV
 			| CURRENT_P
 			| CURSOR
+			| CUSTOM
 			| CYCLE
 			| DATA_P
 			| DATABASE
@@ -13025,6 +13106,7 @@ unreserved_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PLAN
 			| PLANS
 			| PRECEDING
 			| PREPARE
@@ -13035,6 +13117,7 @@ unreserved_keyword:
 			| PROCEDURAL
 			| PROCEDURE
 			| PROGRAM
+			| PROVIDER
 			| QUOTE
 			| RANGE
 			| READ
@@ -13060,6 +13143,7 @@ unreserved_keyword:
 			| ROWS
 			| RULE
 			| SAVEPOINT
+			| SCAN
 			| SCHEMA
 			| SCROLL
 			| SEARCH
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 07e0b98..cbfd3cf 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -202,6 +202,7 @@ check_xact_readonly(Node *parsetree)
 		case T_AlterTableSpaceOptionsStmt:
 		case T_AlterTableSpaceMoveStmt:
 		case T_CreateForeignTableStmt:
+		case T_CreateCustomPlanProviderStmt:
 		case T_ImportForeignSchemaStmt:
 		case T_SecLabelStmt:
 			PreventCommandIfReadOnly(CreateCommandTag(parsetree));
@@ -684,6 +685,14 @@ standard_ProcessUtility(Node *parsetree,
 			AlterEventTrigger((AlterEventTrigStmt *) parsetree);
 			break;
 
+		case  T_CreateCustomPlanProviderStmt:
+			{
+				CreateCustomPlanProviderStmt *cpp_stmt
+					= (CreateCustomPlanProviderStmt *)parsetree;
+				DefineCustomPlanProvider(cpp_stmt);
+			}
+			break;
+
 			/*
 			 * ******************************** ROLE statements ****
 			 */
@@ -1949,6 +1958,9 @@ CreateCommandTag(Node *parsetree)
 				case OBJECT_OPFAMILY:
 					tag = "DROP OPERATOR FAMILY";
 					break;
+				case OBJECT_CPP:
+					tag = "DROP CUSTOM PLAN PROVIDER";
+					break;
 				default:
 					tag = "???";
 			}
@@ -2216,6 +2228,10 @@ CreateCommandTag(Node *parsetree)
 			tag = "ALTER EVENT TRIGGER";
 			break;
 
+		case T_CreateCustomPlanProviderStmt:
+			tag = "CREATE CUSTOM PLAN PROVIDER";
+			break;
+
 		case T_CreatePLangStmt:
 			tag = "CREATE LANGUAGE";
 			break;
@@ -2840,6 +2856,10 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_CreateCustomPlanProviderStmt:
+			lev = LOGSTMT_DDL;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 0781ac8..abe27e2 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -5481,6 +5481,29 @@ 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-plan provider replaced a varno in expression tree by special
+ * varno.
+ */
+static Node *
+GetSpecialCustomVar(PlanState *ps, Var *varnode, PlanState **child_ps)
+{
+	CustomPlanState *cps = (CustomPlanState *) ps;
+
+	Assert(IsA(ps, CustomPlanState));
+	Assert(IS_SPECIAL_VARNO(varnode->varno));
+
+	if (!cps->methods->GetSpecialCustomVar)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("%s does not support special varno reference",
+						cps->methods->CustomName)));
+	return (Node *)cps->methods->GetSpecialCustomVar(cps, varnode, child_ps);
+}
 
 /*
  * Display a Var appropriately.
@@ -5510,6 +5533,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;
 
@@ -5534,6 +5559,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, CustomPlanState) &&
+			 (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;
@@ -5748,6 +5796,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;
 
@@ -5822,6 +5871,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, CustomPlanState) &&
+			 (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/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 94d951c..1619de7 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -32,6 +32,7 @@
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_conversion.h"
+#include "catalog/pg_custom_plan_provider.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_db_role_setting.h"
 #include "catalog/pg_default_acl.h"
@@ -345,6 +346,28 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		8
 	},
+	{CustomPlanProviderRelationId,	/* CUSTOMPLANPROVIDEROID */
+		CustomPlanProviderOidIndexId,
+		1,
+		{
+			ObjectIdAttributeNumber,
+			0,
+			0,
+			0
+		},
+		32
+	},
+	{CustomPlanProviderRelationId,	/* CUSTOMPLANPROVIDERNAME */
+		CustomPlanProviderNameIndexId,
+		1,
+		{
+			Anum_pg_custom_plan_provider_cppname,
+			0,
+			0,
+			0,
+		},
+		32
+	},
 	{DatabaseRelationId,		/* DATABASEOID */
 		DatabaseOidIndexId,
 		1,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 8ed2592..4a7186a 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -147,6 +147,7 @@ typedef enum ObjectClass
 	OCLASS_DEFACL,				/* pg_default_acl */
 	OCLASS_EXTENSION,			/* pg_extension */
 	OCLASS_EVENT_TRIGGER,		/* pg_event_trigger */
+	OCLASS_CPP,					/* pg_custom_plan_provider */
 	MAX_OCLASS					/* MUST BE LAST */
 } ObjectClass;
 
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 59576fd..b926e15 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -299,6 +299,12 @@ DECLARE_UNIQUE_INDEX(pg_extension_name_index, 3081, on pg_extension using btree(
 DECLARE_UNIQUE_INDEX(pg_range_rngtypid_index, 3542, on pg_range using btree(rngtypid oid_ops));
 #define RangeTypidIndexId					3542
 
+DECLARE_UNIQUE_INDEX(pg_custom_plan_provider_oid_index, 3563, on pg_custom_plan_provider using btree(oid oid_ops));
+#define CustomPlanProviderOidIndexId 3563
+
+DECLARE_UNIQUE_INDEX(pg_custom_plan_provider_name_index, 3564, on pg_custom_plan_provider using btree(cppname name_ops));
+#define CustomPlanProviderNameIndexId 3564
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_custom_plan_provider.h b/src/include/catalog/pg_custom_plan_provider.h
new file mode 100644
index 0000000..32d8d19
--- /dev/null
+++ b/src/include/catalog/pg_custom_plan_provider.h
@@ -0,0 +1,56 @@
+/* -------------------------------------------------------------------------
+ *
+ * pg_custom_plan_provider.h
+ *	definition of the system "custom plan provider" relation
+ *  (pg_custom_plan_provider)
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * -------------------------------------------------------------------------
+ */
+#ifndef PG_CUSTOM_PLAN_PROVIDER_H
+#define PG_CUSTOM_PLAN_PROVIDER_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *     pg_custom_plan_provider definition.  cpp turns this into
+ *     typedef struct FormData_pg_custom_plan_provider
+ * ----------------
+ */
+#define CustomPlanProviderRelationId       3562
+
+CATALOG(pg_custom_plan_provider,3562)
+{
+	NameData	cppname;		/* name of custom-plan provider */
+	char		cppclass;		/* class of custom-plan */
+	Oid			cpphandler;		/* function of custom-plan provider */
+} FormData_pg_custom_plan_provider;
+
+/* ----------------
+ *     Form_pg_custom_plan_provider corresponds to a pointer to a tuple
+ *     with the format of pg_custom_plan_provider relation.
+ * ----------------
+ */
+typedef FormData_pg_custom_plan_provider *Form_pg_custom_plan_provider;
+
+/* ----------------
+ *     compiler constants for pg_custom_plan_provider
+ * ----------------
+ */
+#define Natts_pg_custom_plan_provider			3
+#define Anum_pg_custom_plan_provider_cppname	1
+#define Anum_pg_custom_plan_provider_cppclass	2
+#define Anum_pg_custom_plan_provider_cpphandler	3
+
+/* definition of cppclass */
+#define CUSTOMPLAN_CLASS_SCAN			's'
+
+/*
+ * definition of flags in CustomPath/Plan/PlanState node
+ */
+#define CPPFLAG_SUPPORT_MARK_RESTORE		0x0001
+#define CPPFLAG_SUPPORT_BACKWARD_SCAN		0x0002
+
+#endif	/* PG_CUSTOM_PLAN_PROVIDER_H */
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index f8b4a65..fc798a2 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");
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 0ebdbc1..b0f3f0c 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -130,6 +130,11 @@ extern Datum transformGenericOptions(Oid catalogId,
 						List *options,
 						Oid fdwvalidator);
 
+/* commands/custom_plan.c */
+extern Oid get_custom_plan_provider_oid(const char *cpp_name, bool missing_ok);
+extern void	RemoveCustomPlanProviderById(Oid cust_oid);
+extern Oid	DefineCustomPlanProvider(CreateCustomPlanProviderStmt *stmt);
+
 /* support routines in commands/define.c */
 
 extern char *defGetString(DefElem *def);
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 239aff3..b5d05b0 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -102,7 +102,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..abe1e94
--- /dev/null
+++ b/src/include/executor/nodeCustom.h
@@ -0,0 +1,30 @@
+/* ------------------------------------------------------------------------
+ *
+ * nodeCustom.h
+ *
+ * prototypes for CustomPlan 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 CustomPlanState *ExecInitCustomPlan(CustomPlan *cplan,
+										   EState *estate, int eflags);
+extern TupleTableSlot *ExecCustomPlan(CustomPlanState *node);
+extern Node *MultiExecCustomPlan(CustomPlanState *node);
+extern void ExecEndCustomPlan(CustomPlanState *node);
+
+extern void ExecReScanCustomPlan(CustomPlanState *node);
+extern void ExecCustomMarkPos(CustomPlanState *node);
+extern void ExecCustomRestrPos(CustomPlanState *node);
+
+#endif	/* NODECUSTOM_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index b271f21..8838a6d 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,49 @@ typedef struct ForeignScanState
 	void	   *fdw_state;		/* foreign-data wrapper can keep state here */
 } ForeignScanState;
 
+/* ----------------
+ * CustomPlanState information
+ *
+ *		CustomPlan nodes are used to execute custom code within executor.
+ * ----------------
+ */
+struct CustomExecMethods;
+struct ExplainState;	/* to avoid to include explain.h here */
+
+typedef struct CustomPlanState
+{
+	ScanState	ss;
+	int			flags;	/* CPPFLAG_* defined in pg_custom_plan_provider.h */
+	const struct CustomExecMethods *methods;
+} CustomPlanState;
+
+typedef struct CustomExecMethods
+{
+	const char     *CustomName;
+
+	/* EXECUTOR methods */
+	void    (*BeginCustomPlan)(CustomPlanState *node,
+							   EState *estate,
+							   int eflags);
+	TupleTableSlot *(*ExecCustomPlan)(CustomPlanState *node);
+	Node   *(*MultiExecCustomPlan)(CustomPlanState *node);
+	void	(*EndCustomPlan)(CustomPlanState *node);
+	void	(*ReScanCustomPlan)(CustomPlanState *node);
+	void	(*MarkPosCustomPlan)(CustomPlanState *node);
+	void	(*RestrPosCustomPlan)(CustomPlanState *node);
+
+	/* EXPLAIN support */
+	void	(*ExplainCustomPlanTargetRel)(CustomPlanState *node,
+										  struct ExplainState *es);
+	void    (*ExplainCustomPlan)(CustomPlanState *node,
+								 List *ancestors,
+								 struct ExplainState *es);
+	void	(*ExplainCustomPreScanNode)(CustomPlanState *node,
+										Bitmapset **rels_used);
+	Node   *(*GetSpecialCustomVar)(CustomPlanState *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 067c768..b9672b2 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_CustomPlan,
 	T_Join,
 	T_NestLoop,
 	T_MergeJoin,
@@ -107,6 +108,7 @@ typedef enum NodeTag
 	T_CteScanState,
 	T_WorkTableScanState,
 	T_ForeignScanState,
+	T_CustomPlanState,
 	T_JoinState,
 	T_NestLoopState,
 	T_MergeJoinState,
@@ -224,6 +226,7 @@ typedef enum NodeTag
 	T_HashPath,
 	T_TidPath,
 	T_ForeignPath,
+	T_CustomPath,
 	T_AppendPath,
 	T_MergeAppendPath,
 	T_ResultPath,
@@ -366,6 +369,7 @@ typedef enum NodeTag
 	T_RefreshMatViewStmt,
 	T_ReplicaIdentityStmt,
 	T_AlterSystemStmt,
+	T_CreateCustomPlanProviderStmt,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8364bef..1f98cce 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1209,6 +1209,7 @@ typedef enum ObjectType
 	OBJECT_CONSTRAINT,
 	OBJECT_COLLATION,
 	OBJECT_CONVERSION,
+	OBJECT_CPP,
 	OBJECT_DATABASE,
 	OBJECT_DOMAIN,
 	OBJECT_EVENT_TRIGGER,
@@ -2073,6 +2074,18 @@ typedef struct AlterOpFamilyStmt
 } AlterOpFamilyStmt;
 
 /* ----------------------
+ *     Create Custom Plan Provider Statement
+ * ----------------------
+ */
+typedef struct CreateCustomPlanProviderStmt
+{
+	NodeTag     type;
+	char	   *cpp_name;		/* name of custom-plan provider */
+	char		cpp_class;		/* class of custom-plan provides */
+	List	   *cpp_options;	/* generic options for provider */
+} CreateCustomPlanProviderStmt;
+
+/* ----------------------
  *		Drop Table|Sequence|View|Index|Type|Domain|Conversion|Schema Statement
  * ----------------------
  */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 3b9c683..75d4c23 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -15,6 +15,7 @@
 #define PLANNODES_H
 
 #include "access/sdir.h"
+#include "lib/stringinfo.h"
 #include "nodes/bitmapset.h"
 #include "nodes/primnodes.h"
 
@@ -479,6 +480,42 @@ typedef struct ForeignScan
 	bool		fsSystemCol;	/* true if any "system column" is needed */
 } ForeignScan;
 
+/* ----------------
+ *     CustomPlan node
+ * ----------------
+ */
+struct CustomPlanMethods;
+
+struct CustomPath;			/* to avoid to include nodes/relation.h here */
+struct PlannerInfo;			/* to avoid to include nodes/relation.h here */
+
+typedef struct CustomPlan
+{
+	Scan		scan;
+	int			flags;	/* CPPFLAG_* defined in pg_custom_plan_provider.h */
+	const struct CustomPlanMethods *methods;
+} CustomPlan;
+
+typedef struct CustomPlanMethods
+{
+	const char *CustomName;
+	Plan	  *(*InitCustomPlan)(CustomPlan *custom_plan,
+								 struct PlannerInfo *root,
+								 struct CustomPath *best_path,
+								 List *tlist,
+								 List *clauses);
+	void	   (*SetCustomPlanRef)(struct PlannerInfo *root,
+								   CustomPlan *custom_plan,
+								   int rtoffset);
+	void	   (*FinalizeCustomPlan)(struct PlannerInfo *root,
+									 CustomPlan *custom_plan,
+									 bool (*finalize_primnode)(),
+									 void *finalize_context);
+	Node	  *(*CreateCustomPlanState)(CustomPlan *custom_plan);
+	void	   (*TextOutCustomPlan)(StringInfo str,
+									const CustomPlan *node);
+	CustomPlan *(*CopyCustomPlan)(const CustomPlan *from);
+} CustomPlanMethods;
 
 /*
  * ==========
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index dacbe9c..8fb8477 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"
@@ -881,6 +882,32 @@ 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;
+
+typedef struct CustomPath
+{
+	Path        path;
+	int			flags;	/* CPPFLAG_* defined in pg_custom_plan_provider.h */
+	const struct CustomPathMethods *methods;
+} CustomPath;
+
+typedef struct CustomPathMethods
+{
+	const char *CustomName;
+	Node   *(*CreateCustomPlan)(PlannerInfo *root,
+								CustomPath *best_path);
+	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..7cf331c 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -129,6 +129,20 @@ extern Path *reparameterize_path(PlannerInfo *root, Path *path,
 					double loop_count);
 
 /*
+ * interface towards custom-plan provider functions
+ */
+typedef struct {
+	uint32			cpp_class;
+	PlannerInfo	   *root;
+	RelOptInfo	   *baserel;
+	RangeTblEntry  *rte;
+} customScanArg;
+
+extern void call_custom_scan_providers(PlannerInfo *root,
+									   RelOptInfo *baserel,
+									   RangeTblEntry *rte);
+
+/*
  * prototypes for relnode.c
  */
 extern void setup_simple_rel_arrays(PlannerInfo *root);
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 8bdb7db..76e3c86 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
@@ -128,6 +129,7 @@ extern List *remove_useless_joins(PlannerInfo *root, List *joinlist);
  */
 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 set_opfuncid(OpExpr *opexpr);
 extern void set_sa_opfuncid(ScalarArrayOpExpr *opexpr);
 extern void record_plan_function_dependency(PlannerInfo *root, Oid funcid);
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index b52e507..05603e5 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -107,6 +107,7 @@ PG_KEYWORD("current_time", CURRENT_TIME, RESERVED_KEYWORD)
 PG_KEYWORD("current_timestamp", CURRENT_TIMESTAMP, RESERVED_KEYWORD)
 PG_KEYWORD("current_user", CURRENT_USER, RESERVED_KEYWORD)
 PG_KEYWORD("cursor", CURSOR, UNRESERVED_KEYWORD)
+PG_KEYWORD("custom", CUSTOM, UNRESERVED_KEYWORD)
 PG_KEYWORD("cycle", CYCLE, UNRESERVED_KEYWORD)
 PG_KEYWORD("data", DATA_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("database", DATABASE, UNRESERVED_KEYWORD)
@@ -282,6 +283,7 @@ PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD)
 PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD)
+PG_KEYWORD("plan", PLAN, UNRESERVED_KEYWORD)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD)
 PG_KEYWORD("position", POSITION, COL_NAME_KEYWORD)
 PG_KEYWORD("preceding", PRECEDING, UNRESERVED_KEYWORD)
@@ -295,6 +297,7 @@ PG_KEYWORD("privileges", PRIVILEGES, UNRESERVED_KEYWORD)
 PG_KEYWORD("procedural", PROCEDURAL, UNRESERVED_KEYWORD)
 PG_KEYWORD("procedure", PROCEDURE, UNRESERVED_KEYWORD)
 PG_KEYWORD("program", PROGRAM, UNRESERVED_KEYWORD)
+PG_KEYWORD("provider", PROVIDER, UNRESERVED_KEYWORD)
 PG_KEYWORD("quote", QUOTE, UNRESERVED_KEYWORD)
 PG_KEYWORD("range", RANGE, UNRESERVED_KEYWORD)
 PG_KEYWORD("read", READ, UNRESERVED_KEYWORD)
@@ -325,6 +328,7 @@ PG_KEYWORD("row", ROW, COL_NAME_KEYWORD)
 PG_KEYWORD("rows", ROWS, UNRESERVED_KEYWORD)
 PG_KEYWORD("rule", RULE, UNRESERVED_KEYWORD)
 PG_KEYWORD("savepoint", SAVEPOINT, UNRESERVED_KEYWORD)
+PG_KEYWORD("scan", SCAN, UNRESERVED_KEYWORD)
 PG_KEYWORD("schema", SCHEMA, UNRESERVED_KEYWORD)
 PG_KEYWORD("scroll", SCROLL, UNRESERVED_KEYWORD)
 PG_KEYWORD("search", SEARCH, UNRESERVED_KEYWORD)
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index f97229f..e1e56f7 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -52,6 +52,8 @@ enum SysCacheIdentifier
 	CONNAMENSP,
 	CONSTROID,
 	CONVOID,
+	CUSTOMPLANPROVIDEROID,
+	CUSTOMPLANPROVIDERNAME,
 	DATABASEOID,
 	DEFACLROLENSPOBJ,
 	ENUMOID,
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 111d24c..5ebceea 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -97,6 +97,7 @@ pg_class|t
 pg_collation|t
 pg_constraint|t
 pg_conversion|t
+pg_custom_plan_provider|t
 pg_database|t
 pg_db_role_setting|t
 pg_default_acl|t
#18Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Kohei KaiGai (#17)
1 attachment(s)

The attached patch is the rebased custom-plan api, without any
functional changes
from the latest version; that added a flag field to custom-plan node
to show whether
it support mark/restore or backward-scan.

Towards the upcoming commit-fest, let me summarize the brief overview
of this patch.

The purpose of custom-plan interface, implemented with this patch, is to allow
extensions to provide alternative way to scan (and potentially joining
and so on)
relation, in addition to the built-in logic.
If one or more extensions are installed as custom-plan provider, it
can tell the planner
alternative way to scan a relation using CustomPath node with cost estimation.
As usual manner, the planner will chose a particular path based on the cost.
If custom one would not be chosen, it's gone and nothing different.
Once a custom-plan gets chosen, the custom-plan provider that performs on behalf
of the custom-plan node shall be invoked during query execution. It is
responsible to
scan the relation by its own way.
One expected usage of this interface is GPU acceleration that I'm working also.

The custom-plan provider shall be invoked via the function being
installed as custom-
plan provider, with an argument that packs all the necessary
information to construct
a custom-path node. In case of relation scan, customScanArg that contains
PlannerInfo, RelOptInfo and RangeTblEntry shall be informed.
The function is registered using a new command:
CREATE CUSTOM PLAN PROVIDER <name>
FOR SCAN HANDLER <handler_funtion>;

According to the discussion before, CustomXXX node is designed to have private
fields of extension like a manner of object oriented language.
CustomXXX node has a few common and minimum required fields, but no private
pointer. Extension declares its own Path/Plan/PlanState structure that inherits
CustomXXX node on the head of structure declaration, but not all. It can have
private fields on the later half of the structure.
The contrib/ctidscan is a good example to see how extension can utilize the
interface.

Once a CustomPlan/PlanState node is constructed, the rest of processes are
what other executor-nodes are doing. It shall be invoked at beginning, ending
and running of the executor, then callback function in the table of function
pointers shall be called.

Thanks,

2014-07-23 10:47 GMT+09:00 Kohei KaiGai <kaigai@kaigai.gr.jp>:

2014-07-18 10:28 GMT+09:00 Kouhei Kaigai <kaigai@ak.jp.nec.com>:

Alvaro Herrera <alvherre@2ndquadrant.com> writes:

I haven't followed this at all, but I just skimmed over it and noticed
the CustomPlanMarkPos thingy; apologies if this has been discussed
before. It seems a bit odd to me; why isn't it sufficient to have a
boolean flag in regular CustomPlan to indicate that it supports
mark/restore?

Yeah, I thought that was pretty bogus too, but it's well down the list of
issues that were there last time I looked at this ...

IIRC, CustomPlanMarkPos was suggested to keep the interface of
ExecSupportsMarkRestore() that takes plannode tag to determine
whether it support Mark/Restore.
As my original proposition did, it seems to me a flag field in
CustomPlan structure is straightforward, if we don't hesitate to
change ExecSupportsMarkRestore().

The attached patch revised the above point.
It eliminates CustomPlanMarkPos, and adds flags field on CustomXXX
structure to inform the backend whether the custom plan provider can
support mark-restore position and backward scan.
This change requires ExecSupportsMarkRestore() to reference
contents of Path node, not only node-tag, so its declaration was also
changed to take a pointer to Path node.
The only caller of this function is final_cost_mergejoin() right now.
It just gives pathtype field of Path node on its invocation, so this change
does not lead significant degradation.

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

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

Attachments:

pgsql-v9.5-custom-plan.v7.patchapplication/octet-stream; name=pgsql-v9.5-custom-plan.v7.patchDownload
 contrib/Makefile                                  |   1 +
 contrib/ctidscan/Makefile                         |  19 +
 contrib/ctidscan/ctidscan--1.0.sql                |  12 +
 contrib/ctidscan/ctidscan--unpackaged-1.0.sql     |   0
 contrib/ctidscan/ctidscan.c                       | 952 ++++++++++++++++++++++
 contrib/ctidscan/ctidscan.control                 |   5 +
 contrib/ctidscan/expected/ctidscan.out            | 312 +++++++
 contrib/ctidscan/sql/ctidscan.sql                 |  54 ++
 doc/src/sgml/catalogs.sgml                        |  59 ++
 doc/src/sgml/custom-plan-provider.sgml            | 431 ++++++++++
 doc/src/sgml/filelist.sgml                        |   1 +
 doc/src/sgml/postgres.sgml                        |   1 +
 doc/src/sgml/ref/allfiles.sgml                    |   2 +
 doc/src/sgml/ref/create_custom_plan_provider.sgml | 139 ++++
 doc/src/sgml/ref/drop_custom_plan_provider.sgml   | 108 +++
 doc/src/sgml/reference.sgml                       |   2 +
 src/backend/catalog/Makefile                      |   2 +-
 src/backend/catalog/dependency.c                  |  11 +-
 src/backend/catalog/objectaddress.c               |  63 ++
 src/backend/commands/Makefile                     |   2 +-
 src/backend/commands/custom_plan.c                | 188 +++++
 src/backend/commands/dropcmds.c                   |   5 +
 src/backend/commands/event_trigger.c              |   2 +
 src/backend/commands/explain.c                    |  40 +
 src/backend/executor/Makefile                     |   2 +-
 src/backend/executor/execAmi.c                    |  25 +
 src/backend/executor/execProcnode.c               |  19 +
 src/backend/executor/nodeCustom.c                 | 147 ++++
 src/backend/nodes/copyfuncs.c                     |  35 +
 src/backend/nodes/equalfuncs.c                    |  14 +
 src/backend/nodes/outfuncs.c                      |  33 +
 src/backend/optimizer/path/allpaths.c             |  30 +-
 src/backend/optimizer/plan/createplan.c           |  98 ++-
 src/backend/optimizer/plan/setrefs.c              |  11 +-
 src/backend/optimizer/plan/subselect.c            |  24 +
 src/backend/optimizer/util/pathnode.c             | 110 +++
 src/backend/parser/gram.y                         |  94 ++-
 src/backend/tcop/utility.c                        |  20 +
 src/backend/utils/adt/ruleutils.c                 |  73 ++
 src/backend/utils/cache/syscache.c                |  23 +
 src/include/catalog/dependency.h                  |   1 +
 src/include/catalog/indexing.h                    |   6 +
 src/include/catalog/pg_custom_plan_provider.h     |  50 ++
 src/include/catalog/pg_operator.h                 |   3 +
 src/include/commands/defrem.h                     |   5 +
 src/include/executor/nodeCustom.h                 |  30 +
 src/include/nodes/execnodes.h                     |  42 +
 src/include/nodes/nodes.h                         |   5 +
 src/include/nodes/parsenodes.h                    |  13 +
 src/include/nodes/plannodes.h                     |  37 +
 src/include/nodes/relation.h                      |  26 +
 src/include/optimizer/pathnode.h                  |  14 +
 src/include/optimizer/planmain.h                  |   2 +
 src/include/parser/kwlist.h                       |   4 +
 src/include/utils/syscache.h                      |   2 +
 src/test/regress/expected/sanity_check.out        |   1 +
 56 files changed, 3374 insertions(+), 36 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..1e476a6
--- /dev/null
+++ b/contrib/ctidscan/Makefile
@@ -0,0 +1,19 @@
+# contrib/ctidscan/Makefile
+
+MODULES = ctidscan
+
+EXTENSION = ctidscan
+DATA = ctidscan--1.0.sql
+
+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--1.0.sql b/contrib/ctidscan/ctidscan--1.0.sql
new file mode 100644
index 0000000..46420a0
--- /dev/null
+++ b/contrib/ctidscan/ctidscan--1.0.sql
@@ -0,0 +1,12 @@
+--
+-- Create ctidscan handler function
+--
+CREATE FUNCTION ctidscanaddpath(internal)
+  RETURNS pg_catalog.void
+  AS 'MODULE_PATHNAME','CtidScanAddPath'
+  LANGUAGE C STRICT;
+
+--
+-- Create a custom-plan provider
+--
+CREATE CUSTOM PLAN PROVIDER ctidscan FOR scan HANDLER ctidscanaddpath;
diff --git a/contrib/ctidscan/ctidscan--unpackaged-1.0.sql b/contrib/ctidscan/ctidscan--unpackaged-1.0.sql
new file mode 100644
index 0000000..e69de29
diff --git a/contrib/ctidscan/ctidscan.c b/contrib/ctidscan/ctidscan.c
new file mode 100644
index 0000000..5151365
--- /dev/null
+++ b/contrib/ctidscan/ctidscan.c
@@ -0,0 +1,952 @@
+/*
+ * 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_custom_plan_provider.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 {
+	CustomPlan		cplan;
+	List		   *ctid_quals;
+} CtidScanPlan;
+
+typedef struct {
+	CustomPlanState	cps;
+	List		   *ctid_quals;		/* list of ExprState for inequality ops */
+} CtidScanState;
+
+static CustomPathMethods	ctidscan_path_methods;
+static CustomPlanMethods	ctidscan_plan_methods;
+static CustomExecMethods	ctidscan_exec_methods;
+
+/* function declarations */
+void	_PG_init(void);
+Datum	CtidScanAddPath(PG_FUNCTION_ARGS);
+
+#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;
+}
+
+/*
+ * CreateCtidScanPlan - A method of CustomPath; that populate a custom
+ * object being delivered from CustomPlan type, according to the supplied
+ * CustomPath object.
+ */
+static Node *
+CreateCtidScanPlan(PlannerInfo *root, CustomPath *best_path)
+{
+	CtidScanPath   *ctid_path = (CtidScanPath *) best_path;
+	CtidScanPlan   *ctid_scan;
+
+	ctid_scan = palloc0(sizeof(CtidScanPlan));
+	NodeSetTag(ctid_scan, T_CustomPlan);
+	ctid_scan->cplan.methods = &ctidscan_plan_methods;
+	ctid_scan->ctid_quals = ctid_path->ctid_quals;
+
+	return (Node *)&ctid_scan->cplan;
+}
+
+/*
+ * InitCtidScanPlan
+ *
+ * main portion to initialize CustomPlan node.
+ */
+static Plan *
+InitCtidScanPlan(CustomPlan *custom_plan,
+				 PlannerInfo *root, CustomPath *best_path,
+				 List *tlist, List *clauses)
+{
+	CtidScanPlan   *ctid_scan = (CtidScanPlan *)custom_plan;
+	List		   *ctid_quals = ((CtidScanPath *)best_path)->ctid_quals;
+
+	Assert(ctid_scan->cplan.scan.scanrelid > 0);
+
+	/* Set targetlist as is */
+	ctid_scan->cplan.scan.plan.targetlist = tlist;
+	/* Reduce RestrictInfo list to bare expressions */
+	ctid_scan->cplan.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 &ctid_scan->cplan.scan.plan;
+}
+
+/*
+ * 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 CustomPlan; 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,
+				   CustomPlan *custom_plan,
+				   int rtoffset)
+{
+	CtidScanPlan   *ctidscan = (CtidScanPlan *) custom_plan;
+	Scan		   *scan = &ctidscan->cplan.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);
+}
+
+/*
+ * SupportCtidBackwardScan - A method of CustomPlan; that informs the core
+ * backend whether this custom-plan node support backward scan or not.
+ */
+static bool
+SupportCtidBackwardScan(CustomPlan *custom_plan)
+{
+	return true;
+}
+
+/*
+ * FinalizeCtidScanPlan - A method of CustomPlan; that handles callbacks
+ * by finalize_plan().
+ */
+static void
+FinalizeCtidScanPlan(PlannerInfo *root,
+					 CustomPlan *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 CustomPlan; that populate a custom
+ * object being delivered from CustomPlanState type, according to the
+ * supplied CustomPath object.
+ */
+static Node *
+CreateCtidScanState(CustomPlan *custom_plan)
+{
+	CtidScanState  *ctss = palloc0(sizeof(CtidScanState));
+
+	NodeSetTag(ctss, T_CustomPlanState);
+	ctss->cps.methods = &ctidscan_exec_methods;
+
+	return (Node *)&ctss->cps;
+}
+
+/*
+ * TextOutCtidScanPlan - A method of CustomPlan; that generates text
+ * representation of the given object.
+ */
+static void
+TextOutCtidScanPlan(StringInfo str, const CustomPlan *node)
+{
+	CtidScanPlan   *ctid_plan = (CtidScanPlan *) node;
+
+	appendStringInfo(str, " :ctid_quals %s",
+					 nodeToString(ctid_plan->ctid_quals));
+}
+
+/*
+ * CopyCtidScanPlan - A method of CustomPlan; that create a copied object.
+ */
+static CustomPlan *
+CopyCtidScanPlan(const CustomPlan *from)
+{
+	CtidScanPlan   *oldnode = (CtidScanPlan *) from;
+	CtidScanPlan   *newnode = palloc0(sizeof(CtidScanPlan));
+
+	NodeSetTag(newnode, T_CustomPlan);
+	newnode->cplan.methods = oldnode->cplan.methods;
+	newnode->ctid_quals = copyObject(oldnode->ctid_quals);
+
+	return &newnode->cplan;
+}
+
+/*
+ * BeginCtidScan - A method of CustomPlanState; that initializes
+ * the supplied CtidScanState object, at beginning of the executor.
+ */
+static void
+BeginCtidScan(CustomPlanState *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->cps.ss.ss_currentScanDesc = NULL;
+}
+
+/*
+ * ReScanCtidScan - A method of CustomPlanState; that rewind the current
+ * seek position.
+ */
+static void
+ReScanCtidScan(CustomPlanState *node)
+{
+	CtidScanState  *ctss = (CtidScanState *)node;
+	HeapScanDesc	scan = ctss->cps.ss.ss_currentScanDesc;
+	EState		   *estate = node->ss.ps.state;
+	ScanDirection	direction = estate->es_direction;
+	Relation		relation = ctss->cps.ss.ss_currentRelation;
+	ExprContext	   *econtext = ctss->cps.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->cps.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->cps.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->cps.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(CustomPlanState *node)
+{
+	CtidScanState  *ctss = (CtidScanState *) node;
+	HeapScanDesc	scan;
+	TupleTableSlot *slot;
+	EState		   *estate = node->ss.ps.state;
+	ScanDirection	direction = estate->es_direction;
+	HeapTuple		tuple;
+
+	if (!ctss->cps.ss.ss_currentScanDesc)
+		ReScanCtidScan(node);
+	scan = ctss->cps.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->cps.ss.ss_ScanTupleSlot;
+	ExecStoreTuple(tuple, slot, scan->rs_cbuf, false);
+
+	return slot;
+}
+
+static bool
+CTidRecheckCustomScan(CustomPlanState *node, TupleTableSlot *slot)
+{
+	return true;
+}
+
+/*
+ * ExecCtidScan - A method of CustomPlanState; that fetches a tuple
+ * from the relation, if exist anymore.
+ */
+static TupleTableSlot *
+ExecCtidScan(CustomPlanState *node)
+{
+	return ExecScan(&node->ss,
+					(ExecScanAccessMtd) CTidAccessCustomScan,
+					(ExecScanRecheckMtd) CTidRecheckCustomScan);
+}
+
+/*
+ * CTidEndCustomScan - A method of CustomPlanState; that closes heap and
+ * scan descriptor, and release other related resources.
+ */
+static void
+EndCtidScan(CustomPlanState *node)
+{
+	CtidScanState  *ctss = (CtidScanState *)node;
+
+	if (ctss->cps.ss.ss_currentScanDesc)
+		heap_endscan(ctss->cps.ss.ss_currentScanDesc);
+}
+
+/*
+ * ExplanCtidScanTargetRel - A method of CustomPlanState; that output
+ * relation's name to be scanned.
+ */
+static void
+ExplanCtidScanTargetRel(CustomPlanState *node, ExplainState *es)
+{
+	CtidScanState  *ctss = (CtidScanState *) node;
+	CtidScanPlan   *ctid_plan = (CtidScanPlan *) ctss->cps.ss.ps.plan;
+	Index			rti = ctid_plan->cplan.scan.scanrelid;
+	RangeTblEntry   *rte;
+	char		   *objectname = NULL;
+	char		   *namespace = NULL;
+	char		   *refname;
+
+	/* logic copied from ExplainTargetRel */
+	rte = rt_fetch(rti, es->rtable);
+	refname = (char *) list_nth(es->rtable_names, rti - 1);
+	if (refname == NULL)
+		refname = rte->eref->aliasname;
+
+	Assert(rte->rtekind == RTE_RELATION);
+	objectname = get_rel_name(rte->relid);
+	if (es->verbose)
+		namespace = get_namespace_name(get_rel_namespace(rte->relid));
+
+	if (es->format == EXPLAIN_FORMAT_TEXT)
+	{
+		appendStringInfoString(es->str, " on");
+		if (namespace != NULL)
+			appendStringInfo(es->str, " %s.%s", quote_identifier(namespace),
+							 quote_identifier(objectname));
+		else if (objectname != NULL)
+			appendStringInfo(es->str, " %s", quote_identifier(objectname));
+		if (objectname == NULL || strcmp(refname, objectname) != 0)
+			appendStringInfo(es->str, " %s", quote_identifier(refname));
+	}
+	else
+	{
+		if (objectname != NULL)
+			ExplainPropertyText("Relation Name", objectname, es);
+		if (namespace != NULL)
+			ExplainPropertyText("Schema", namespace, es);
+		ExplainPropertyText("Alias", refname, es);
+	}
+}
+
+/*
+ * ExplainCtidScan - A method of CustomPlanState; that shows extra info
+ * on EXPLAIN command.
+ */
+static void
+ExplainCtidScan(CustomPlanState *node, List *ancestors, ExplainState *es)
+{
+	CtidScanState  *ctss = (CtidScanState *) node;
+	CtidScanPlan   *ctid_plan = (CtidScanPlan *) ctss->cps.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);
+	}
+}
+
+/*
+ * ExplainCtidPreScanNode - A method of CustomPlanState; that informs
+ * the core backend relation's rtindex to be referenced, prior to the
+ * main EXPLAIN processing.
+ */
+static void
+ExplainCtidPreScanNode(CustomPlanState *node, Bitmapset **rels_used)
+{
+	CtidScanState  *ctss = (CtidScanState *) node;
+	Index			scanrelid = ((Scan *)ctss->cps.ss.ps.plan)->scanrelid;
+
+	*rels_used = bms_add_member(*rels_used, scanrelid);
+}
+
+/*
+ * Entrypoint of this extension
+ */
+Datum
+CtidScanAddPath(PG_FUNCTION_ARGS)
+{
+	customScanArg  *cscan_arg = (customScanArg *)PG_GETARG_POINTER(0);
+	PlannerInfo	   *root;
+	RangeTblEntry  *rte;
+	RelOptInfo	   *baserel;
+	char			relkind;
+	ListCell	   *lc;
+	List		   *ctid_quals = NIL;
+
+	if (cscan_arg->custom_class != CUSTOMPLAN_CLASS_SCAN)
+		PG_RETURN_VOID();
+
+	root = cscan_arg->root;
+	rte = cscan_arg->rte;
+	baserel = cscan_arg->baserel;
+
+	/* all we can support is regular relations */
+	if (rte->rtekind != RTE_RELATION)
+		PG_RETURN_VOID();
+
+	relkind = get_rel_relkind(rte->relid);
+	if (relkind != RELKIND_RELATION &&
+		relkind != RELKIND_MATVIEW &&
+		relkind != RELKIND_TOASTVALUE)
+		PG_RETURN_VOID();
+
+	/* 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_CustomPlan;
+		ctid_path->cpath.path.parent = baserel;
+		ctid_path->cpath.path.param_info
+			= get_baserel_parampathinfo(root, baserel, required_outer);
+		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);
+	}
+	PG_RETURN_VOID();
+}
+PG_FUNCTION_INFO_V1(CtidScanAddPath);
+
+/*
+ * Entrypoint of this extension
+ */
+void
+_PG_init(void)
+{
+	/* setup ctidscan_path_methods */
+	ctidscan_path_methods.CustomName = "ctidscan";
+	ctidscan_path_methods.CreateCustomPlan = CreateCtidScanPlan;
+	ctidscan_path_methods.TextOutCustomPath = TextOutCtidScanPath;
+
+	/* setup ctidscan_plan_methods */
+	ctidscan_plan_methods.CustomName = "ctidscan";
+	ctidscan_plan_methods.InitCustomPlan = InitCtidScanPlan;
+	ctidscan_plan_methods.SetCustomPlanRef = SetCtidScanPlanRef;
+	ctidscan_plan_methods.SupportBackwardScan = SupportCtidBackwardScan;
+	ctidscan_plan_methods.FinalizeCustomPlan = FinalizeCtidScanPlan;
+	ctidscan_plan_methods.CreateCustomPlanState = CreateCtidScanState;
+	ctidscan_plan_methods.TextOutCustomPlan = TextOutCtidScanPlan;
+	ctidscan_plan_methods.CopyCustomPlan = CopyCtidScanPlan;
+
+	/* setup ctidscan_planstate_methods */
+	ctidscan_exec_methods.CustomName = "ctidscan";
+	ctidscan_exec_methods.BeginCustomPlan = BeginCtidScan;
+	ctidscan_exec_methods.ExecCustomPlan = ExecCtidScan;
+	ctidscan_exec_methods.EndCustomPlan = EndCtidScan;
+	ctidscan_exec_methods.ReScanCustomPlan = ReScanCtidScan;
+	ctidscan_exec_methods.MarkPosCustomPlan = NULL;
+	ctidscan_exec_methods.RestrPosCustomPlan = NULL;
+	ctidscan_exec_methods.ExplainCustomPlanTargetRel = ExplanCtidScanTargetRel;
+	ctidscan_exec_methods.ExplainCustomPlan = ExplainCtidScan;
+	ctidscan_exec_methods.ExplainCustomPreScanNode = ExplainCtidPreScanNode;
+	ctidscan_exec_methods.GetSpecialCustomVar = NULL;
+}
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..74d66e6
--- /dev/null
+++ b/contrib/ctidscan/expected/ctidscan.out
@@ -0,0 +1,312 @@
+--
+-- Regression Tests for Custom Plan APIs
+--
+CREATE EXTENSION ctidscan;
+-- 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                              
+----------------------------------------------------------------------
+ Custom (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 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 (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 (ctidscan) on t1
+               Filter: (ctid < '(2,10)'::tid)
+               ctid quals: (ctid < '(2,10)'::tid)
+   ->  Sort
+         Sort Key: t2.ctid
+         ->  Custom (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 (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 (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)
+
+-- 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..213a97a
--- /dev/null
+++ b/contrib/ctidscan/sql/ctidscan.sql
@@ -0,0 +1,54 @@
+--
+-- Regression Tests for Custom Plan APIs
+--
+
+CREATE EXTENSION ctidscan;
+
+-- 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;
+
+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);
+
+-- Test cleanup
+DROP SCHEMA regtest_custom_scan CASCADE;
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 68f8434..1e68c30 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -109,6 +109,11 @@
      </row>
 
      <row>
+      <entry><link linkend="catalog-pg-custom-plan-provider"><structname>pg_custom_plan_provider</structname></link></entry>
+      <entry>custom plan providers</entry>
+     </row>
+
+     <row>
       <entry><link linkend="catalog-pg-conversion"><structname>pg_conversion</structname></link></entry>
       <entry>encoding conversion information</entry>
      </row>
@@ -2508,6 +2513,60 @@
 
  </sect1>
 
+ <sect1 id="catalog-pg-custom-plan-provider">
+  <title><structname>pg_custom_plan_provider</structname></title>
+
+  <indexterm zone="catalog-pg-custom-plan-provider">
+   <primary>pg_custom_plan_provider</primary>
+  </indexterm>
+
+  <para>
+   The catalog <structname>pg_custom_plan_provider</structname> describes
+   custom-plan providers. See <xref linkend="sql-createcustomplanprovider">
+   for more information.
+  </para>
+
+  <table>
+   <title><structname>pg_custom_plan_provider</> Columns</title>
+   <tgroup cols="4">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>References</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><structfield>oid</structfield></entry>
+      <entry><type>oid</type></entry>
+      <entry></entry>
+      <entry>Row identifier (hidden attribute; must be explicitly selected)</entry>
+     </row>
+     <row>
+      <entry><structfield>cppname</structfield></entry>
+      <entry><type>name</type></entry>
+      <entry></entry>
+      <entry>custom-plan provider name</entry>
+     </row>
+     <row>
+      <entry><structfield>cppclass</structfield></entry>
+      <entry><type>char</type></entry>
+      <entry></entry>
+      <entry>class of custom-plan node on which this custom-plan provider can perform</entry>
+     </row>
+     <row>
+      <entry><structfield>cpphandler</structfield></entry>
+      <entry><type>regproc</type></entry>
+      <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
+      <entry>handler function of custom-plan provder that will propose an alternative query execution path</entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+ </sect1>
+
  <sect1 id="catalog-pg-database">
   <title><structname>pg_database</structname></title>
 
diff --git a/doc/src/sgml/custom-plan-provider.sgml b/doc/src/sgml/custom-plan-provider.sgml
new file mode 100644
index 0000000..d86495b
--- /dev/null
+++ b/doc/src/sgml/custom-plan-provider.sgml
@@ -0,0 +1,431 @@
+<!-- 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 variable 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 the extension that provides custom-plan custom plan
+  provider.
+ </para>
+
+ <sect1 id="cpp-overall-steps">
+  <title>Custom Plan Overall Steps</title>
+  <para>
+   A custom plan provider can be registered using
+   <xref linkend="sql-createcustomplanprovider"> command.
+   It takes a class of custom plan and its handler function.
+   Class of custom plan specifies the task to be replaced by the custom-
+   plan being defined.
+   Only <literal>SCAN</> is the supported class of custom-plan right now.
+   Custom plan handler function is an entrypoint to the planner. It calls
+   the handler function during construction of query execution path.
+  </para>
+  <para>
+   Handler function has to be declared as a function that takes one
+   <literal>internal</> data type and returns <literal>void</>.
+   On invocation, query planner delivers a pointer of data structure
+   according to the custom plan class, custom-plan handler function will
+   decide whether it can offer alternative execution path towards the
+   required task. If available, it can add a <literal>CustomPath</>
+   (or inherited object type) as one of the candidate execution path
+   with estimated cost and set of callbacks defined in the
+   <literal>CustomPathMethods</> structure.
+  </para>
+  <para>
+   Planner compares all the potential execution paths based on its cost.
+   Once a custom path that was added by custom plan provider gets chosen,
+   <literal>CreateCustomPlan</> callback shall be called, to populate
+   a <literal>CustomPlan</> (or inherited object type) node according to
+   the <literal>CustomPath</> node preliminary constructed.
+   In the similar manner, <literal>CreateCustomPlanState</> callback shall
+   be called, to populate a <literal>CustomPlanState</> (or inherited
+   object type) node according to the <literal>CustomPlan</> node being
+   constructed above.
+   The reason why custom-plan provider has to allocate a node object by
+   itself is that we allow to extend the base types to store private
+   fields managed by individual custom-plan providers, thus only custom-
+   plan provider knows actual data size to be allocated.
+  </para>
+  <para>
+   Once a <literal>CustomPlanState</> is constructed, its callback
+   functions are invoked by executor, so custom-plan provider performs
+   required tasks then pops up the result.
+  </para>
+ </sect1>
+
+ <sect1 id="cpp-handler-function">
+  <title>Custom Plan Handler Functions</title>
+  <para>
+   A handler function that was specified on
+   the <xref linkend="sql-createcustomplanprovider"> command is
+   declared to return <literal>void</> data type and takes an
+   <literal>internal</> data type; that is usually applied to exchange
+   internal data structure.
+   This handler function is not an exception. It can reference a pointer
+   being informed via function argument to understand the context.
+  </para>
+  <para>
+   The data structure of arguments fully depend on the class of custom-
+   plan. In case of <literal>SCAN</> class (that is only supported one
+   right now), the first argument points the following data structure
+   that has enough information to construct a <literal>Path</> node.
+<programlisting>
+typedef struct {
+    uint32          cpp_class;
+    PlannerInfo    *root;
+    RelOptInfo     *baserel;
+    RangeTblEntry  *rte;
+} customScanArg;
+</programlisting>
+   <literal>cpp_class</>is always <literal>CUSTOMPLAN_CLASS_SCAN</>
+   that shows this structure is for scan class.
+   <literal>root</> is <literal>PlannerInfo</> of this plan,
+   <literal>baserel</> is <literal>RelOptInfo</> of the relation to
+   be scanned, and <literal>rte</> is <literal>RangeTblEntry</> of
+   the relation.
+  </para>
+  <para>
+   The custom-plan provider being invoked can check whether it can
+   provides alternative way to scan the relation. If available, it
+   shall construct <literal>CustomPath</> or its inherited one with
+   estimated cost and callbacks below, then register the path using
+   <literal>add_path</> towards the supplied <literal>RelOptInfo</>.
+  </para>
+ </sect1>
+
+ <sect1 id="cpp-path-callbacks">
+  <title>Custom Path Callbacks</title>
+  <para>
+   This section introduces callback functions of <literal>CustomPath</>
+   structure; defined in the <literal>CustomPathMethods</>.
+  </para>
+  <para>
+<programlisting>
+Node *
+CreateCustomPlan(PlannerInfo *root,
+                 CustomPath *best_path);
+</programlisting>
+   It populates a <literal>CustomPlan</> (or inherited data type) node
+   according to the supplied <literal>CustomPath</> node which was
+   constructed on the custom plan handler function then chosen by the
+   query planner.
+   Only custom plan provider can know exact size of the node to be
+   allocated, this callback allocate a <literal>CustomPlan</> node with
+   <literal>CustomPlanMethods</> callbacks table and arbitrary private
+   fields.
+  </para>
+  <para>
+   Note that the node tag can be assigned is either <literal>CustomPlan</>
+   or <literal>CustomPlanMarkPos</>. The later one indicates this plan
+   node supports mark and restore position during query execution, but
+   has identical data structure.
+  </para>
+
+
+  <para>
+<programlisting>
+void
+TextOutCustomPath(StringInfo str, const CustomPath *node);
+</programlisting>
+   It makes a text representation of custom path node. If custom-plan
+   provider extends <literal>CustomPath</> data type, it shall to put private
+   fields on the supplied <literal>StringInfo</> with text form.
+   Note that common fields in <literal>CustomPath</> are handled by backend,
+   so extension needs to do nothing special.
+  </para>
+ </sect1>
+
+ <sect1 id="cpp-plan-callbacks">
+  <title>Custom Plan Callbacks</title>
+  <para>
+   This section introduces callback functions of <literal>CustomPlan</>
+   structure; defined in the <literal>CustomPlanMethods</>.
+  </para>
+  <para>
+<programlisting>
+Plan      *
+InitCustomPlan(CustomPlan *custom_plan,
+               PlannerInfo *root,
+               CustomPath *best_path,
+               List *tlist,
+               List *clauses);
+</programlisting>
+   It initializes the <literal>CustomPlan</> node being acquired by
+   <literal>CreateCustomPlan</> callback.
+   The backend takes some common initializations prior to its invocation.
+   <literal>tlist</> and <literal>clauses</> are extracted from the path
+   node according to the usual manner, so all custom plan provider has to
+   do is putting these members if nothing special are done.
+  </para>
+
+  <para>
+<programlisting>
+void
+SetCustomPlanRef(PlannerInfo *root,
+                 CustomPlan *custom_plan,
+                 int rtoffset);
+</programlisting>
+   It adjusts <literal>varno</> and <literal>varattno</> of var-nodes in
+   the expression tree chained from <literal>CustomPlan</> 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 <literal>fix_scan_expr</> shall be called on expression nodes to
+   track plan dependency.
+  </para>
+
+  <para>
+<programlisting>
+bool
+SupportBackwardScan(CustomPlan *custom_plan);
+</programlisting>
+   It is an optional callback, which tells the backend whether this custom-
+   plan node supports backward scan or not. If <literal>NULL</>, it means
+   backward scan is not supported.
+  </para>
+
+  <para>
+<programlisting>
+void
+FinalizeCustomPlan(PlannerInfo *root,
+                   CustomPlan *custom_plan,
+                   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 custom-plan 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 custom-plan 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 *
+CreateCustomPlanState(CustomPlan *custom_plan);
+</programlisting>
+   It populates a <literal>CustomPlanState</> (or inherited data type)
+   node according to the supplied <literal>CustomPlan</> node preliminary
+   constructed, on the beginning of query executor.
+   Only custom plan provider can know exact size of the node to be
+   allocated, this callback allocate a <literal>CustomPlanState</> node
+   with <literal>CustomExecMethods</> callbacks table and arbitrary
+   private fields.
+  </para>
+  <para>
+   Note that main purpose of this callback is allocation of
+   <literal>CustomPlanState</> node, not initialization of individual
+   fields because it shall be handled on the <literal>BeginCustomPlan</>
+   callback to be invoked next to the common usual initialization.
+  </para>
+
+  <para>
+<programlisting>
+void
+TextOutCustomPlan(StringInfo str,
+                  const CustomPlan *node);
+</programlisting>
+   It makes a text representation of custom plan node. If custom-plan
+   provider extends <literal>CustomPlan</> data type, it shall put private
+   fields on the supplied <literal>StringInfo</> with text form.
+   Note that common fields in <literal>CustomPlan</> are handled by backend,
+   so extension needs to do nothing special.
+  </para>
+
+  <para>
+<programlisting>
+CustomPlan *
+CopyCustomPlan(const CustomPlan *from);
+</programlisting>
+   It duplicate a <literal>CustomPlan</> node onto a newly allocated one.
+   If custom-plan provider extends the <literal>CustomPlan</> node, it shall
+   copy the private fields and callback table but no need to copy the common
+   <literal>scan</> field in the <literal>CustomPlan</> data type.
+  </para>
+ </sect1>
+
+ <sect1 id="cpp-exec-callbacks">
+  <title>Custom Executor Callbacks</title>
+  <para>
+   This section introduces callback functions of <literal>CustomPlanState</>
+   structure; defined in the <literal>CustomExecMethods</>.
+  </para>
+  <para>
+<programlisting>
+void
+BeginCustomPlan(CustomPlanState *node,
+                EState *estate,
+                int eflags);
+</programlisting>
+   It begins execution of custom-plan. This callback is invoked during
+   executor startup to initialize the supplied <literal>CustomPlanState</>
+   that was constructed on the <literal>CreateCustomPlanState</> above.
+   The custom-plan provider shall have initialization of its private fields
+   and common fields within <literal>CustomPlanState</> if needed, because
+   backend code already initializes expressions on its <literal>targetlist</>
+   and <literal>qual</>, assigns result slot according to the
+   <literal>targetlist</> and also assigns scan slot if <literal>scanrelid</>
+   is valid.
+  </para>
+
+  <para>
+<programlisting>
+TupleTableSlot *
+ExecCustomPlan(CustomPlanState *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
+   <literal>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 <literal>EState</>, to acquire per-scan
+   duration memory.
+  </para>
+
+  <para>
+<programlisting>
+Node *
+MultiExecCustomPlan(CustomPlanState *node);
+</programlisting>
+   It allows to return arbitrary data structure to the upper node, unlike
+   usual <literal>ExecCustomPlan</>. Built-in code has no invocation path
+   to call <literal>MultiExecProcNode</> towards <literal>CustomPlanState</>
+   node, so it is invoked only when a particular custom-plan provider made
+   a stacked custom-plan nodes and called <literal>MultiExecProcNode</> to
+   this underlying node.
+  </para>
+  <para>
+   Note that it is custom-plan provider's responsibility to translate the
+   arbitrary data structure into <productname>PostgreSQL</>'s complianced
+   data structure when top-level <literal>CustomPlanState</> returns a row
+   using <literal>ExecCustomPlan</>.
+  </para>
+
+  <para>
+<programlisting>
+void
+EndCustomPlan(CustomPlanState *node);
+</programlisting>
+   It ends the execution of custom plan and release any resources held by
+   this node. If custom-plan provider acquired resources that is not
+   released automatically at end of executor, it is responsibility of the
+   custom plan provider.
+  </para>
+
+  <para>
+<programlisting>
+void
+ReScanCustomPlan(CustomPlanState *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 *
+MarkPosCustomPlan(CustomPlanState *node);
+</programlisting>
+   It is an optional callback, if <literal>CustomPlanState</> was populated
+   from the <literal>CustomPlanMarkPos</>. Elsewhere, it shall not be called
+   and <literal>NULL</> is suitable.
+   It saves current scan position on somewhere in private fields of
+   <literal>CustomPlanState</>, to restore the position later.
+  </para>
+
+  <para>
+<programlisting>
+void *
+RestrPosCustomPlan(CustomPlanState *node);
+</programlisting>
+   It is an optional callback, if <literal>CustomPlanState</> was populated
+   from the <literal>CustomPlanMarkPos</>. Elsewhere, it shall not be called
+   and <literal>NULL</> is suitable.
+   It restores the previous scan position saved by
+   the <literal>MarkPosCustomPlan</> above.
+  </para>
+
+  <para>
+<programlisting>
+void
+ExplainCustomPlanTargetRel(CustomPlanState *node,
+                           ExplainState *es);
+</programlisting>
+   It is an optional callback, to show the target relation to be scanned.
+   In most cases, custom plan provider put text representation of the relation
+   to be scanned according to the manner in <literal>ExplainTargetRel</>.
+  </para>
+
+  <para>
+<programlisting>
+void
+ExplainCustomPlan(CustomPlanState *node,
+                  List *ancestors,
+                  ExplainState *es);
+</programlisting>
+   It is an optional callback, to show custom-plan specific explain output.
+  </para>
+
+  <para>
+<programlisting>
+void
+ExplainCustomPreScanNode(CustomPlanState *node,
+                         Bitmapset **rels_used);
+</programlisting>
+   It is an optional callback, to inform the backend which relation is
+   referenced. It shall set <literal>scanrelid</> of the target relation.
+   If <literal>NULL</>, it means this custom-plan provider never
+   references base relations.
+  </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>CustomPlanState</> when <literal>EXPLAIN</> needs the
+   text form of the column actually referenced.
+   In case when custom-plan provider adjusted <literal>varno</> of varnodes
+   on the expression tree to use special varnos (<literal>INNER_VAR</>,
+   <literal>OUTER_VAR</> or <literal>INDEX_VAR</>), custom-plan 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 <literal>PlanState</> that shall be
+   set on the <literal>child_ps</> argument.
+  </para>
+ </sect1>
+</chapter>
+
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 5902f97..a9b9efc 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-provider SYSTEM "custom-plan-provider.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..4702178 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -242,6 +242,7 @@
   &nls;
   &plhandler;
   &fdwhandler;
+  &custom-plan-provider;
   &geqo;
   &indexam;
   &gist;
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index b685e16..ab35742 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -55,6 +55,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY createCast         SYSTEM "create_cast.sgml">
 <!ENTITY createCollation    SYSTEM "create_collation.sgml">
 <!ENTITY createConversion   SYSTEM "create_conversion.sgml">
+<!ENTITY createCustomPlanProvider SYSTEM "create_custom_plan_provider.sgml">
 <!ENTITY createDatabase     SYSTEM "create_database.sgml">
 <!ENTITY createDomain       SYSTEM "create_domain.sgml">
 <!ENTITY createEventTrigger SYSTEM "create_event_trigger.sgml">
@@ -95,6 +96,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY dropCast           SYSTEM "drop_cast.sgml">
 <!ENTITY dropCollation      SYSTEM "drop_collation.sgml">
 <!ENTITY dropConversion     SYSTEM "drop_conversion.sgml">
+<!ENTITY dropCustomPlanProvider SYSTEM "drop_custom_plan_provider.sgml">
 <!ENTITY dropDatabase       SYSTEM "drop_database.sgml">
 <!ENTITY dropDomain         SYSTEM "drop_domain.sgml">
 <!ENTITY dropEventTrigger   SYSTEM "drop_event_trigger.sgml">
diff --git a/doc/src/sgml/ref/create_custom_plan_provider.sgml b/doc/src/sgml/ref/create_custom_plan_provider.sgml
new file mode 100644
index 0000000..0816584
--- /dev/null
+++ b/doc/src/sgml/ref/create_custom_plan_provider.sgml
@@ -0,0 +1,139 @@
+<!--
+doc/src/sgml/ref/create_custom_plan_provider.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-CREATECUSTOMPLANPROVIDER">
+ <indexterm zone="sql-createcustomplanprovider">
+  <primary>CREATE CUSTOM PLAN PROVIDER</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>CREATE CUSTOM PLAN PROVIDER</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>CREATE CUSTOM PLAN PROVIDER</refname>
+  <refpurpose>define a new custom plan provider</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+CREATE CUSTOM PLAN PROVIDER <replaceable class="parameter">cpp_name</replaceable> FOR <replaceable class="parameter">cpp_class</replaceable>
+    HANDLER <replaceable class="parameter">handler_function</replaceable>
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>CREATE CUSTOM PLAN PROVIDER</command> defines a new custom-plan
+   provider.
+   The user who defines the custom-plan provider has to be a superuser.
+  </para>
+
+  <para>
+   A custom-plan provider can offer the query planner alternative options
+   to scan relation, or potentially join relations and so on, in addition
+   to the built-in logics. It is usually extension modules that implement
+   callbacks according to the custom-plan interface.
+  </para>
+  <para>
+   This statement defines a couple of an entrypoint of custom-plan provider
+   and its supporting workload type. Right now, <literal>scan</literal> is
+   the only class being supported; that enables to call extension's
+   callback during query execution instead of built-in routines like
+   <literal>SeqScan</literal> or <literal>IndexScan</literal> if its
+   cost estimation is enough reasonable.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">cpp_name</replaceable></term>
+    <listitem>
+     <para>
+      The name of the custom-plan provider to be created.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">cpp_class</replaceable></term>
+    <listitem>
+     <para>
+      Workload type on which custom-plan provider can perform.
+      Only <literal>SCAN</literal> is supported option right now.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">handler_function</replaceable></term>
+    <listitem>
+     <para>
+      A function to be called when query planner is finding the best path
+      to scan a relation.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Notes</title>
+
+  <para>
+   The function that performs as a custom-plan provider shall be declared
+   to return <literal>void</> and take one argument with <literal>internal</>
+   data type.
+   The core <productname>PostgreSQL</> calls custom-plan provider function
+   with a set of information about planner's state and relation(s) to be
+   scanned, then this function shall check whether it can offer alternative
+   scan paths or not.
+   If available, it constructs a path object derived from
+   <literal>CustomPath</> structure, that contains a set of callbacks
+   including the ones to populate <literal>CustomPlan</> or
+   <literal>CustomPlanState</> object later.
+   If extension needs to save its private information in these object,
+   define a new structure that extends above data types.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+  <para>
+   Create a custom-plan provider <literal>ctidscan</> that uses the funcion
+   <literal>ctidscanaddpath</>.   
+<programlisting>
+CREATE CUSTOM PLAN PROVIDER ctidscan FOR scan HANDLER ctidscanaddpath;
+</programlisting>
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+  <para>
+   There is no <command>CREATE CUSTOM PLAN</command> command
+   in the SQL standard.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-dropcustomplanprovider"></member>
+  </simplelist>
+  <simplelist type="inline">
+   <member><xref linkend="custom-plan"></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/ref/drop_custom_plan_provider.sgml b/doc/src/sgml/ref/drop_custom_plan_provider.sgml
new file mode 100644
index 0000000..6a305a8
--- /dev/null
+++ b/doc/src/sgml/ref/drop_custom_plan_provider.sgml
@@ -0,0 +1,108 @@
+<!--
+doc/src/sgml/ref/drop_custom_plan_provider.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-DROPCUSTOMPLANPROVIDER">
+ <indexterm zone="sql-dropcustomplanprovider">
+  <primary>DROP CUSTOM PLAN PROVIDER</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>DROP CUSTOM PLAN PROVIDER</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>DROP CUSTOM PLAN PROVIDER</refname>
+  <refpurpose>remove a custom-plan provider</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+DROP CUSTOM PLAN PROVIDER [ IF EXISTS ] <replaceable class="parameter">cpp_name</replaceable> [ CASCADE | RESTRICT ]
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>DROP CUSTOM PLAN PROVIDER</command> removes an existing custom
+   plan provider. To execute this command, the current user must be superuser.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><literal>IF EXISTS</literal></term>
+    <listitem>
+     <para>
+      Do not throw an error if the custom-plan provider does not exist.
+      A notice is issued in this case.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name of an existing custom-plan provider.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>CASCADE</literal></term>
+    <listitem>
+     <para>
+      Automatically drop objects that depend on the custom-plan provider.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>RESTRICT</literal></term>
+    <listitem>
+     <para>
+      Refuse to drop the custom-plan provider if any objects depend on it.
+      This is the default.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+  <para>
+   Drop a custom-plan provider <literal>foo</> if it exists:
+<programlisting>
+DROP CUSTOM PLAN PROVIDER IF EXISTS foo;
+</programlisting></para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   There is no <command>DROP CUSTOM PLAN</command> command
+   in the SQL standard.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-createcustomplanprovider"></member>
+  </simplelist>
+ </refsect1>
+
+</refentry>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index 6ec1263..1a3dbdd 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -83,6 +83,7 @@
    &createCast;
    &createCollation;
    &createConversion;
+   &createCustomPlanProvider;
    &createDatabase;
    &createDomain;
    &createEventTrigger;
@@ -123,6 +124,7 @@
    &dropCast;
    &dropCollation;
    &dropConversion;
+   &dropCustomPlanProvider;
    &dropDatabase;
    &dropDomain;
    &dropEventTrigger;
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index a974bd5..f7e29eb 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -39,7 +39,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
 	pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
 	pg_ts_parser.h pg_ts_template.h pg_extension.h \
 	pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
-	pg_foreign_table.h \
+	pg_foreign_table.h pg_custom_plan_provider.h \
 	pg_default_acl.h pg_seclabel.h pg_shseclabel.h pg_collation.h pg_range.h \
 	toasting.h indexing.h \
     )
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index d41ba49..496bc9a 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -30,6 +30,7 @@
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_conversion.h"
 #include "catalog/pg_conversion_fn.h"
+#include "catalog/pg_custom_plan_provider.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_default_acl.h"
 #include "catalog/pg_depend.h"
@@ -154,7 +155,8 @@ static const Oid object_classes[MAX_OCLASS] = {
 	UserMappingRelationId,		/* OCLASS_USER_MAPPING */
 	DefaultAclRelationId,		/* OCLASS_DEFACL */
 	ExtensionRelationId,		/* OCLASS_EXTENSION */
-	EventTriggerRelationId		/* OCLASS_EVENT_TRIGGER */
+	EventTriggerRelationId,		/* OCLASS_EVENT_TRIGGER */
+	CustomPlanProviderRelationId,		/* OCLASS_CPP */
 };
 
 
@@ -1249,6 +1251,10 @@ doDeletion(const ObjectAddress *object, int flags)
 			RemoveEventTriggerById(object->objectId);
 			break;
 
+		case OCLASS_CPP:
+			RemoveCustomPlanProviderById(object->objectId);
+			break;
+
 		default:
 			elog(ERROR, "unrecognized object class: %u",
 				 object->classId);
@@ -2316,6 +2322,9 @@ getObjectClass(const ObjectAddress *object)
 
 		case EventTriggerRelationId:
 			return OCLASS_EVENT_TRIGGER;
+
+		case CustomPlanProviderRelationId:
+			return OCLASS_CPP;
 	}
 
 	/* shouldn't get here */
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index d143a44..be01b2a 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -30,6 +30,7 @@
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_conversion.h"
+#include "catalog/pg_custom_plan_provider.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_extension.h"
 #include "catalog/pg_foreign_data_wrapper.h"
@@ -152,6 +153,18 @@ static const ObjectPropertyType ObjectProperty[] =
 		true
 	},
 	{
+		CustomPlanProviderRelationId,
+		CustomPlanProviderOidIndexId,
+		CUSTOMPLANPROVIDEROID,
+		CUSTOMPLANPROVIDERNAME,
+		Anum_pg_custom_plan_provider_cppname,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		-1,
+		false,
+	},
+	{
 		DatabaseRelationId,
 		DatabaseOidIndexId,
 		DATABASEOID,
@@ -529,6 +542,7 @@ get_object_address(ObjectType objtype, List *objname, List *objargs,
 			case OBJECT_FDW:
 			case OBJECT_FOREIGN_SERVER:
 			case OBJECT_EVENT_TRIGGER:
+			case OBJECT_CPP:
 				address = get_object_address_unqualified(objtype,
 														 objname, missing_ok);
 				break;
@@ -755,6 +769,9 @@ get_object_address_unqualified(ObjectType objtype,
 			case OBJECT_EVENT_TRIGGER:
 				msg = gettext_noop("event trigger name cannot be qualified");
 				break;
+			case OBJECT_CPP:
+				msg = gettext_noop("custom plan provider name cannot be qualified");
+				break;
 			default:
 				elog(ERROR, "unrecognized objtype: %d", (int) objtype);
 				msg = NULL;		/* placate compiler */
@@ -815,6 +832,11 @@ get_object_address_unqualified(ObjectType objtype,
 			address.objectId = get_event_trigger_oid(name, missing_ok);
 			address.objectSubId = 0;
 			break;
+		case OBJECT_CPP:
+			address.classId = CustomPlanProviderRelationId;
+			address.objectId = get_custom_plan_provider_oid(name, missing_ok);
+			address.objectSubId = 0;
+			break;
 		default:
 			elog(ERROR, "unrecognized objtype: %d", (int) objtype);
 			/* placate compiler, which doesn't know elog won't return */
@@ -1295,6 +1317,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
 			break;
 		case OBJECT_TSPARSER:
 		case OBJECT_TSTEMPLATE:
+		case OBJECT_CPP:
 			/* We treat these object types as being owned by superusers */
 			if (!superuser_arg(roleid))
 				ereport(ERROR,
@@ -2166,6 +2189,24 @@ getObjectDescription(const ObjectAddress *object)
 				break;
 			}
 
+		case OCLASS_CPP:
+			{
+				Form_pg_custom_plan_provider cpp_form;
+				HeapTuple	tup;
+
+				tup = SearchSysCache1(CUSTOMPLANPROVIDEROID,
+									  ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR,
+						 "cache lookup failed for custom-plan provider %u",
+						 object->objectId);
+				cpp_form = (Form_pg_custom_plan_provider) GETSTRUCT(tup);
+				appendStringInfo(&buffer, _("custom plan provider %s"),
+								 NameStr(cpp_form->cppname));
+				ReleaseSysCache(tup);
+				break;
+			}
+
 		default:
 			appendStringInfo(&buffer, "unrecognized object %u %u %d",
 							 object->classId,
@@ -2577,6 +2618,10 @@ getObjectTypeDescription(const ObjectAddress *object)
 			appendStringInfoString(&buffer, "event trigger");
 			break;
 
+		case OCLASS_CPP:
+			appendStringInfoString(&buffer, "custom plan provider");
+			break;
+
 		default:
 			appendStringInfo(&buffer, "unrecognized %u", object->classId);
 			break;
@@ -3330,6 +3375,24 @@ getObjectIdentity(const ObjectAddress *object)
 				break;
 			}
 
+		case OCLASS_CPP:
+			{
+				HeapTuple	tup;
+				Form_pg_custom_plan_provider cpp_form;
+
+				tup = SearchSysCache1(CUSTOMPLANPROVIDEROID,
+									  ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR,
+						 "cache lookup failed for custom-plan provider %u",
+						 object->objectId);
+				cpp_form = (Form_pg_custom_plan_provider) GETSTRUCT(tup);
+				appendStringInfoString(&buffer,
+						 quote_identifier(NameStr(cpp_form->cppname)));
+				ReleaseSysCache(tup);
+				break;
+			}
+
 		default:
 			appendStringInfo(&buffer, "unrecognized object %u %u %d",
 							 object->classId,
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index 22f116b..1e8e6f4 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -14,7 +14,7 @@ include $(top_builddir)/src/Makefile.global
 
 OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o  \
 	collationcmds.o constraint.o conversioncmds.o copy.o createas.o \
-	dbcommands.o define.o discard.o dropcmds.o \
+	custom_plan.o dbcommands.o define.o discard.o dropcmds.o \
 	event_trigger.o explain.o extension.o foreigncmds.o functioncmds.o \
 	indexcmds.o lockcmds.o matview.o operatorcmds.o opclasscmds.o \
 	portalcmds.o prepare.o proclang.o \
diff --git a/src/backend/commands/custom_plan.c b/src/backend/commands/custom_plan.c
new file mode 100644
index 0000000..2a81a7b
--- /dev/null
+++ b/src/backend/commands/custom_plan.c
@@ -0,0 +1,188 @@
+/*-------------------------------------------------------------------------
+ *
+ * custom_plan.c
+ *		custom plan nodes creation/manipulation commands
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ *    src/backend/commands/custom_plan.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_custom_plan_provider.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+#include "commands/defrem.h"
+#include "fmgr.h"
+#include "miscadmin.h"
+#include "parser/parse_func.h"
+#include "utils/builtins.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+/*
+ * utility function to lookup a custom-plan provider by name
+ */
+Oid
+get_custom_plan_provider_oid(const char *cpp_name, bool missing_ok)
+{
+	Oid		cpp_oid;
+
+	cpp_oid = GetSysCacheOid1(CUSTOMPLANPROVIDERNAME,
+							   CStringGetDatum(cpp_name));
+	if (!OidIsValid(cpp_oid) && !missing_ok)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("custom-plan provider \"%s\" does not exist",
+						cpp_name)));
+	return cpp_oid;
+}
+
+/*
+ * Drop a custom-plan provider
+ */
+void
+RemoveCustomPlanProviderById(Oid cpp_oid)
+{
+	Relation	rel;
+	HeapTuple	tuple;
+
+	rel = heap_open(CustomPlanProviderRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCache1(CUSTOMPLANPROVIDEROID,
+							ObjectIdGetDatum(cpp_oid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for custom-plan provider %u",
+			 cpp_oid);
+
+	simple_heap_delete(rel, &tuple->t_self);
+
+	ReleaseSysCache(tuple);
+
+	heap_close(rel, RowExclusiveLock);
+}
+
+/*
+ * Create a custom-plan provider
+ */
+Oid
+DefineCustomPlanProvider(CreateCustomPlanProviderStmt *stmt)
+{
+	Relation	rel;
+	Oid			cpp_oid;
+	Oid			cpp_handler = InvalidOid;
+	Datum		values[Natts_pg_custom_plan_provider];
+	bool		isnull[Natts_pg_custom_plan_provider];
+	HeapTuple	tuple;
+	ListCell   *cell;
+	ObjectAddress myself;
+	ObjectAddress referenced;
+
+	rel = heap_open(CustomPlanProviderRelationId, RowExclusiveLock);
+
+	/* must be super user */
+	if (!superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+			 errmsg("permission denied to create custom-plan provider \"%s\"",
+					stmt->cpp_name),
+			 errhint("Must be superuser to create a custom-plan node.")));
+
+	/* check namespace conflicts */
+	cpp_oid = get_custom_plan_provider_oid(stmt->cpp_name, true);
+	if (OidIsValid(cpp_oid))
+		ereport(ERROR,
+				(errcode(ERRCODE_DUPLICATE_OBJECT),
+				 errmsg("custom-plan provider \"%s\" already exists",
+						stmt->cpp_name)));
+
+	/* check custom-plan class */
+	if (stmt->cpp_class != CUSTOMPLAN_CLASS_SCAN)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("unexpected custom plan class specified: %d",
+						(int)stmt->cpp_class)));
+
+	/* parse custom-plan options */
+	foreach (cell, stmt->cpp_options)
+	{
+		DefElem	   *defel = lfirst(cell);
+
+		Assert(IsA(defel, DefElem));
+
+		if (strcmp(defel->defname, "handler") == 0)
+		{
+			Oid		argtypes[1];
+
+			if (OidIsValid(cpp_handler))
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+
+			argtypes[0] = INTERNALOID;
+			cpp_handler = LookupFuncName((List *)defel->arg,
+										 1, argtypes, false);
+			if (get_func_rettype(cpp_handler) != VOIDOID)
+				ereport(ERROR,
+						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+						 errmsg("function %s must return type \"void\"",
+								NameListToString((List *) defel->arg))));
+		}
+		else
+			elog(ERROR, "unexpected custom-plan provider option: %s",
+				 defel->defname);
+	}
+
+	if (!OidIsValid(cpp_handler))
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("HANDLER must be provided")));
+
+	/*
+	 * Insert tuple into pg_custom_plan system catalog
+	 */
+	memset(values, 0, sizeof(values));
+	memset(isnull, 0, sizeof(isnull));
+	values[Anum_pg_custom_plan_provider_cppname - 1]
+		= DirectFunctionCall1(namein, CStringGetDatum(stmt->cpp_name));
+	values[Anum_pg_custom_plan_provider_cppclass - 1]
+		= stmt->cpp_class;
+	values[Anum_pg_custom_plan_provider_cpphandler - 1]
+		= ObjectIdGetDatum(cpp_handler);
+
+	tuple = heap_form_tuple(RelationGetDescr(rel), values, isnull);
+
+	cpp_oid = simple_heap_insert(rel, tuple);
+	CatalogUpdateIndexes(rel, tuple);
+
+	heap_freetuple(tuple);
+
+	/* record dependencies */
+	myself.classId = CustomPlanProviderRelationId;
+	myself.objectId = cpp_oid;
+	myself.objectSubId = 0;
+
+	referenced.classId = ProcedureRelationId;
+	referenced.objectId = cpp_handler;
+	referenced.objectSubId = 0;
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+	/* Post creation hook for new custom-plan provider */
+	InvokeObjectPostCreateHook(CustomPlanProviderRelationId, cpp_oid, 0);
+
+	heap_close(rel, RowExclusiveLock);
+
+	return cpp_oid;
+}
diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c
index e64ad80..c6d4576 100644
--- a/src/backend/commands/dropcmds.c
+++ b/src/backend/commands/dropcmds.c
@@ -408,6 +408,11 @@ does_not_exist_skipping(ObjectType objtype, List *objname, List *objargs)
 				args = strVal(linitial(objargs));
 			}
 			break;
+		case OBJECT_CPP:
+			msg = gettext_noop("custom-plan provider \"%s\" does not exist, skipping");
+			name = NameListToString(objname);
+			break;
+
 		default:
 			elog(ERROR, "unexpected object type (%d)", (int) objtype);
 			break;
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 754264e..76968a2 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -923,6 +923,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
 		case OBJECT_CONSTRAINT:
 		case OBJECT_COLLATION:
 		case OBJECT_CONVERSION:
+		case OBJECT_CPP:
 		case OBJECT_DOMAIN:
 		case OBJECT_EXTENSION:
 		case OBJECT_FDW:
@@ -975,6 +976,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
 		case OCLASS_COLLATION:
 		case OCLASS_CONSTRAINT:
 		case OCLASS_CONVERSION:
+		case OCLASS_CPP:
 		case OCLASS_DEFAULT:
 		case OCLASS_LANGUAGE:
 		case OCLASS_LARGEOBJECT:
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 781a736..7ae2160 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -722,6 +722,15 @@ ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
 			*rels_used = bms_add_member(*rels_used,
 										((Scan *) plan)->scanrelid);
 			break;
+		case T_CustomPlan:
+		case T_CustomPlanMarkPos:
+			{
+				CustomPlanState *cps = (CustomPlanState *)planstate;
+
+				if (cps->methods->ExplainCustomPreScanNode)
+					cps->methods->ExplainCustomPreScanNode(cps, rels_used);
+			}
+			break;
 		case T_ModifyTable:
 			/* cf ExplainModifyTarget */
 			*rels_used = bms_add_member(*rels_used,
@@ -848,6 +857,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 +946,14 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_ForeignScan:
 			pname = sname = "Foreign Scan";
 			break;
+		case T_CustomPlan:
+			sname = "Custom";
+			custom_name = ((CustomPlan *) plan)->methods->CustomName;
+			if (custom_name)
+				pname = psprintf("Custom (%s)", custom_name);
+			else
+				pname = sname;
+			break;
 		case T_Material:
 			pname = sname = "Materialize";
 			break;
@@ -1037,6 +1055,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", custom_name, es);
 	}
 
 	switch (nodeTag(plan))
@@ -1084,6 +1104,14 @@ ExplainNode(PlanState *planstate, List *ancestors,
 					ExplainPropertyText("Index Name", indexname, es);
 			}
 			break;
+		case T_CustomPlan:
+			{
+				CustomPlanState *cps = (CustomPlanState *)planstate;
+
+				if (cps->methods->ExplainCustomPlanTargetRel)
+					cps->methods->ExplainCustomPlanTargetRel(cps, es);
+			}
+			break;
 		case T_ModifyTable:
 			ExplainModifyTarget((ModifyTable *) plan, es);
 			break;
@@ -1353,6 +1381,18 @@ ExplainNode(PlanState *planstate, List *ancestors,
 										   planstate, es);
 			show_foreignscan_info((ForeignScanState *) planstate, es);
 			break;
+		case T_CustomPlan:
+			{
+				CustomPlanState *cps = (CustomPlanState *) planstate;
+
+				show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
+				if (plan->qual)
+					show_instrumentation_count("Rows Removed by Filter", 1,
+											   planstate, es);
+				if (cps->methods->ExplainCustomPlan)
+					cps->methods->ExplainCustomPlan(cps, 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..800c969 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -21,6 +21,7 @@
 #include "executor/nodeBitmapIndexscan.h"
 #include "executor/nodeBitmapOr.h"
 #include "executor/nodeCtescan.h"
+#include "executor/nodeCustom.h"
 #include "executor/nodeForeignscan.h"
 #include "executor/nodeFunctionscan.h"
 #include "executor/nodeGroup.h"
@@ -197,6 +198,10 @@ ExecReScan(PlanState *node)
 			ExecReScanForeignScan((ForeignScanState *) node);
 			break;
 
+		case T_CustomPlanState:
+			ExecReScanCustomPlan((CustomPlanState *) node);
+			break;
+
 		case T_NestLoopState:
 			ExecReScanNestLoop((NestLoopState *) node);
 			break;
@@ -291,6 +296,10 @@ ExecMarkPos(PlanState *node)
 			ExecValuesMarkPos((ValuesScanState *) node);
 			break;
 
+		case T_CustomPlanState:
+			ExecCustomMarkPos((CustomPlanState *) node);
+			break;
+
 		case T_MaterialState:
 			ExecMaterialMarkPos((MaterialState *) node);
 			break;
@@ -348,6 +357,10 @@ ExecRestrPos(PlanState *node)
 			ExecValuesRestrPos((ValuesScanState *) node);
 			break;
 
+		case T_CustomPlanState:
+			ExecCustomRestrPos((CustomPlanState *) node);
+			break;
+
 		case T_MaterialState:
 			ExecMaterialRestrPos((MaterialState *) node);
 			break;
@@ -390,6 +403,7 @@ ExecSupportsMarkRestore(NodeTag plantype)
 		case T_ValuesScan:
 		case T_Material:
 		case T_Sort:
+		case T_CustomPlanMarkPos:
 			return true;
 
 		case T_Result:
@@ -465,6 +479,17 @@ ExecSupportsBackwardScan(Plan *node)
 			return ExecSupportsBackwardScan(((SubqueryScan *) node)->subplan) &&
 				TargetListSupportsBackwardScan(node->targetlist);
 
+		case T_CustomPlan:
+			{
+				CustomPlan *cplan = (CustomPlan *) node;
+
+				if (!TargetListSupportsBackwardScan(node->targetlist))
+					return false;
+				if (cplan->methods->SupportBackwardScan)
+					return cplan->methods->SupportBackwardScan(cplan);
+			}
+			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..62ebab9 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,12 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
 													   estate, eflags);
 			break;
 
+		case T_CustomPlan:
+		case T_CustomPlanMarkPos:
+			result = (PlanState *) ExecInitCustomPlan((CustomPlan *) node,
+													  estate, eflags);
+			break;
+
 			/*
 			 * join nodes
 			 */
@@ -442,6 +449,10 @@ ExecProcNode(PlanState *node)
 			result = ExecForeignScan((ForeignScanState *) node);
 			break;
 
+		case T_CustomPlanState:
+			result = ExecCustomPlan((CustomPlanState *) node);
+			break;
+
 			/*
 			 * join nodes
 			 */
@@ -558,6 +569,10 @@ MultiExecProcNode(PlanState *node)
 			result = MultiExecBitmapOr((BitmapOrState *) node);
 			break;
 
+		case T_CustomPlanState:
+			result = MultiExecCustomPlan((CustomPlanState *) node);
+			break;
+
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
 			result = NULL;
@@ -678,6 +693,10 @@ ExecEndNode(PlanState *node)
 			ExecEndForeignScan((ForeignScanState *) node);
 			break;
 
+		case T_CustomPlanState:
+			ExecEndCustomPlan((CustomPlanState *) node);
+			break;
+
 			/*
 			 * join nodes
 			 */
diff --git a/src/backend/executor/nodeCustom.c b/src/backend/executor/nodeCustom.c
new file mode 100644
index 0000000..785909e
--- /dev/null
+++ b/src/backend/executor/nodeCustom.c
@@ -0,0 +1,147 @@
+/* ------------------------------------------------------------------------
+ *
+ * nodeCustom.c
+ *		Routines to handle execution of custom plan 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"
+
+CustomPlanState *
+ExecInitCustomPlan(CustomPlan *cplan, EState *estate, int eflags)
+{
+	CustomPlanState    *cps;
+
+	/* populate a CustomPlanState according to the CustomPlan */
+	cps = (CustomPlanState *)cplan->methods->CreateCustomPlanState(cplan);
+	Assert(IsA(cps, CustomPlanState));
+
+	/* fill up fields of PlanState */
+	cps->ss.ps.plan = &cplan->scan.plan;
+	cps->ss.ps.state = estate;
+
+	/* create expression context for node */
+	ExecAssignExprContext(estate, &cps->ss.ps);
+	cps->ss.ps.ps_TupFromTlist = false;
+
+	/* initialize child expressions */
+	cps->ss.ps.targetlist = (List *)
+		ExecInitExpr((Expr *) cplan->scan.plan.targetlist,
+					 (PlanState *) cps);
+	cps->ss.ps.qual = (List *)
+		ExecInitExpr((Expr *) cplan->scan.plan.qual,
+					 (PlanState *) cps);
+
+	/* initialization of result tuple slot  */
+	ExecInitResultTupleSlot(estate, &cps->ss.ps);
+	ExecAssignResultTypeFromTL(&cps->ss.ps);
+
+	if (cplan->scan.scanrelid > 0)
+	{
+		Relation	heap_rel;
+
+		heap_rel = ExecOpenScanRelation(estate, cplan->scan.scanrelid, eflags);
+		cps->ss.ss_currentRelation = heap_rel;
+		cps->ss.ss_currentScanDesc = NULL;	/* set by provider on demand */
+		ExecInitScanTupleSlot(estate, &cps->ss);
+		ExecAssignScanType(&cps->ss, RelationGetDescr(heap_rel));
+		ExecAssignScanProjectionInfo(&cps->ss);
+	}
+	else
+	{
+		/*
+		 * Elsewhere, custom-plan provider should be responsible to put
+		 * appropriate initialization of scan tuple-slot and projection
+		 * info.
+		 */
+		cps->ss.ss_currentRelation = NULL;
+		cps->ss.ss_currentScanDesc = NULL;
+		cps->ss.ss_ScanTupleSlot = NULL;
+		cps->ss.ps.ps_ProjInfo = NULL;
+	}
+	/*
+	 * Then, custom-plan provider can have all the own original
+	 * initialization on demand.
+	 */
+	cps->methods->BeginCustomPlan(cps, estate, eflags);
+
+	return cps;
+}
+
+TupleTableSlot *
+ExecCustomPlan(CustomPlanState *cpstate)
+{
+	Assert(cpstate->methods->ExecCustomPlan != NULL);
+	return cpstate->methods->ExecCustomPlan(cpstate);
+}
+
+Node *
+MultiExecCustomPlan(CustomPlanState *cpstate)
+{
+	if (!cpstate->methods->MultiExecCustomPlan)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("CustomPlan \"%s\" does not support MultiExec method",
+						cpstate->methods->CustomName)));
+	return cpstate->methods->MultiExecCustomPlan(cpstate);
+}
+
+void
+ExecEndCustomPlan(CustomPlanState *cpstate)
+{
+	Assert(cpstate->methods->EndCustomPlan != NULL);
+	cpstate->methods->EndCustomPlan(cpstate);
+
+	/* Free the exprcontext */
+	ExecFreeExprContext(&cpstate->ss.ps);
+
+	/* Clean out the tuple table */
+	ExecClearTuple(cpstate->ss.ps.ps_ResultTupleSlot);
+	if (cpstate->ss.ss_ScanTupleSlot)
+		ExecClearTuple(cpstate->ss.ss_ScanTupleSlot);
+
+	/* Close the heap relation, if needed */
+	if (cpstate->ss.ss_currentRelation)
+		ExecCloseScanRelation(cpstate->ss.ss_currentRelation);
+}
+
+void
+ExecReScanCustomPlan(CustomPlanState *cpstate)
+{
+	Assert(cpstate->methods->ReScanCustomPlan != NULL);
+	cpstate->methods->ReScanCustomPlan(cpstate);
+}
+
+void
+ExecCustomMarkPos(CustomPlanState *cpstate)
+{
+	if (!cpstate->methods->MarkPosCustomPlan)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+		errmsg("MarkPos is not supported by custom plan provider: %s",
+			   cpstate->methods->CustomName)));
+	cpstate->methods->MarkPosCustomPlan(cpstate);
+}
+
+void
+ExecCustomRestrPos(CustomPlanState *cpstate)
+{
+	if (!cpstate->methods->RestrPosCustomPlan)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+		errmsg("RestrPos is not supported by custom plan provider: %s",
+			   cpstate->methods->CustomName)));
+	cpstate->methods->RestrPosCustomPlan(cpstate);
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 3088578..b78c6ec 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -598,6 +598,21 @@ _copyForeignScan(const ForeignScan *from)
 }
 
 /*
+ * _copyCustomPlan
+ */
+static CustomPlan *
+_copyCustomPlan(const CustomPlan *from)
+{
+	CustomPlan *newnode;
+
+	newnode = from->methods->CopyCustomPlan(from);
+	Assert(nodeTag(newnode) == nodeTag(from));
+	CopyScanFields((const Scan *) from, (Scan *) newnode);
+
+	return newnode;
+}
+
+/*
  * CopyJoinFields
  *
  *		This function copies the fields of the Join node.  It is used by
@@ -3849,6 +3864,19 @@ _copyAlterTSConfigurationStmt(const AlterTSConfigurationStmt *from)
 	return newnode;
 }
 
+static CreateCustomPlanProviderStmt *
+_copyCreateCustomPlanProviderStmt(const CreateCustomPlanProviderStmt *from)
+{
+	CreateCustomPlanProviderStmt *newnode
+		= makeNode(CreateCustomPlanProviderStmt);
+
+	COPY_STRING_FIELD(cpp_name);
+	COPY_SCALAR_FIELD(cpp_class);
+	COPY_NODE_FIELD(cpp_options);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *					pg_list.h copy functions
  * ****************************************************************
@@ -4012,6 +4040,10 @@ copyObject(const void *from)
 		case T_ForeignScan:
 			retval = _copyForeignScan(from);
 			break;
+		case T_CustomPlan:
+		case T_CustomPlanMarkPos:
+			retval = _copyCustomPlan(from);
+			break;
 		case T_Join:
 			retval = _copyJoin(from);
 			break;
@@ -4561,6 +4593,9 @@ copyObject(const void *from)
 		case T_AlterTSConfigurationStmt:
 			retval = _copyAlterTSConfigurationStmt(from);
 			break;
+		case T_CreateCustomPlanProviderStmt:
+			retval = _copyCreateCustomPlanProviderStmt(from);
+			break;
 
 		case T_A_Expr:
 			retval = _copyAExpr(from);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 1b07db6..bdd626e 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2008,6 +2008,17 @@ _equalAlterTSConfigurationStmt(const AlterTSConfigurationStmt *a,
 }
 
 static bool
+_equalCreateCustomPlanProviderStmt(const CreateCustomPlanProviderStmt *a,
+								   const CreateCustomPlanProviderStmt *b)
+{
+	COMPARE_STRING_FIELD(cpp_name);
+	COMPARE_SCALAR_FIELD(cpp_class);
+	COMPARE_NODE_FIELD(cpp_options);
+
+	return true;
+}
+
+static bool
 _equalAExpr(const A_Expr *a, const A_Expr *b)
 {
 	COMPARE_SCALAR_FIELD(kind);
@@ -3025,6 +3036,9 @@ equal(const void *a, const void *b)
 		case T_AlterTSConfigurationStmt:
 			retval = _equalAlterTSConfigurationStmt(a, b);
 			break;
+		case T_CreateCustomPlanProviderStmt:
+			retval = _equalCreateCustomPlanProviderStmt(a, b);
+			break;
 
 		case T_A_Expr:
 			retval = _equalAExpr(a, b);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e686a6c..ff71988 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -564,6 +564,22 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
 }
 
 static void
+_outCustomPlan(StringInfo str, const CustomPlan *node)
+{
+	if (IsA(node, CustomPlan))
+		WRITE_NODE_TYPE("CUSTOMPLAN");
+	else if (IsA(node, CustomPlanMarkPos))
+		WRITE_NODE_TYPE("CUSTOMPLANMARKPOS");
+	else
+		elog(ERROR, "unexpected node tag given: %d", (int)nodeTag(node));
+
+	_outScanInfo(str, (const Scan *) node);
+	appendStringInfo(str, " :methods");
+	_outToken(str, node->methods->CustomName);
+	node->methods->TextOutCustomPlan(str, node);
+}
+
+static void
 _outJoin(StringInfo str, const Join *node)
 {
 	WRITE_NODE_TYPE("JOIN");
@@ -1582,6 +1598,16 @@ _outForeignPath(StringInfo str, const ForeignPath *node)
 }
 
 static void
+_outCustomPath(StringInfo str, const CustomPath *node)
+{
+	WRITE_NODE_TYPE("CUSTOMPATH");
+	_outPathInfo(str, (const Path *) node);
+	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");
@@ -2851,6 +2877,10 @@ _outNode(StringInfo str, const void *obj)
 			case T_ForeignScan:
 				_outForeignScan(str, obj);
 				break;
+			case T_CustomPlan:
+			case T_CustomPlanMarkPos:
+				_outCustomPlan(str, obj);
+				break;
 			case T_Join:
 				_outJoin(str, obj);
 				break;
@@ -3059,6 +3089,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..dbba5ae 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -336,7 +336,7 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 				}
 				break;
 			case RTE_SUBQUERY:
-				/* Subquery --- fully handled during set_rel_size */
+				/* Subquery --- path was added during set_rel_size */
 				break;
 			case RTE_FUNCTION:
 				/* RangeFunction */
@@ -347,12 +347,17 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 				set_values_pathlist(root, rel, rte);
 				break;
 			case RTE_CTE:
-				/* CTE reference --- fully handled during set_rel_size */
+				/* CTE reference --- path was added during set_rel_size */
 				break;
 			default:
 				elog(ERROR, "unexpected rtekind: %d", (int) rel->rtekind);
 				break;
 		}
+		/* Also, consider paths by custom-plan providers */
+		call_custom_scan_providers(root, rel, rte);
+
+		/* Select cheapest path */
+		set_cheapest(rel);
 	}
 
 #ifdef OPTIMIZER_DEBUG
@@ -401,9 +406,6 @@ set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 
 	/* Consider TID scans */
 	create_tidscan_paths(root, rel);
-
-	/* Now find the cheapest of the paths for this rel */
-	set_cheapest(rel);
 }
 
 /*
@@ -429,9 +431,6 @@ set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 {
 	/* Call the FDW's GetForeignPaths function to generate path(s) */
 	rel->fdwroutine->GetForeignPaths(root, rel, rte->relid);
-
-	/* Select cheapest path */
-	set_cheapest(rel);
 }
 
 /*
@@ -1272,9 +1271,6 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
 
 	/* Generate appropriate path */
 	add_path(rel, create_subqueryscan_path(root, rel, pathkeys, required_outer));
-
-	/* Select cheapest path (pretty easy in this case...) */
-	set_cheapest(rel);
 }
 
 /*
@@ -1343,9 +1339,6 @@ set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 	/* Generate appropriate path */
 	add_path(rel, create_functionscan_path(root, rel,
 										   pathkeys, required_outer));
-
-	/* Select cheapest path (pretty easy in this case...) */
-	set_cheapest(rel);
 }
 
 /*
@@ -1366,9 +1359,6 @@ set_values_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 
 	/* Generate appropriate path */
 	add_path(rel, create_valuesscan_path(root, rel, required_outer));
-
-	/* Select cheapest path (pretty easy in this case...) */
-	set_cheapest(rel);
 }
 
 /*
@@ -1435,9 +1425,6 @@ set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 
 	/* Generate appropriate path */
 	add_path(rel, create_ctescan_path(root, rel, required_outer));
-
-	/* Select cheapest path (pretty easy in this case...) */
-	set_cheapest(rel);
 }
 
 /*
@@ -1488,9 +1475,6 @@ set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 
 	/* Generate appropriate path */
 	add_path(rel, create_worktablescan_path(root, rel, required_outer));
-
-	/* Select cheapest path (pretty easy in this case...) */
-	set_cheapest(rel);
 }
 
 /*
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 4b641a2..9939ad8 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -77,13 +77,13 @@ 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 Plan *create_custom_plan(PlannerInfo *root, CustomPath *best_path);
 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,
 					  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);
@@ -261,6 +261,9 @@ create_plan_recurse(PlannerInfo *root, Path *best_path)
 			plan = create_unique_plan(root,
 									  (UniquePath *) best_path);
 			break;
+		case T_CustomPlan:
+			plan = create_custom_plan(root, (CustomPath *) best_path);
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) best_path->pathtype);
@@ -1072,6 +1075,97 @@ 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 'scan_clauses' and targetlist 'tlist'.
+ */
+static Plan *
+create_custom_plan(PlannerInfo *root, CustomPath *best_path)
+{
+	CustomPlan	   *custom_plan;
+	RelOptInfo	   *rel;
+	List		   *tlist = NIL;
+	List		   *clauses = NIL;
+
+	/*
+	 * Create a custom-plan object delivered from CustomPlan type,
+	 * according to the supplied CustomPath
+	 */
+	Assert(best_path->path.pathtype == T_CustomPlan ||
+		   best_path->path.pathtype == T_CustomPlanMarkPos);
+	custom_plan = (CustomPlan *)
+		best_path->methods->CreateCustomPlan(root, best_path);
+	Assert(nodeTag(custom_plan) == best_path->path.pathtype);
+
+	rel = best_path->path.parent;
+	if (rel)
+	{
+		if (rel->reloptkind == RELOPT_BASEREL)
+		{
+			Assert(rel->relid > 0);
+			custom_plan->scan.scanrelid = rel->relid;
+
+			/*
+			 * For table scans, rather than using the relation targetlist
+			 * (which is only those Vars actually needed by the query),
+			 * we prefer to generate a tlist containing all Vars in order.
+			 * This will allow the executor to optimize away projection of
+			 * the table tuples, if possible.
+			 */
+			if (use_physical_tlist(root, rel))
+				tlist = build_physical_tlist(root, rel);
+		}
+		/* elsewhere, we generate a tlist from the relation targetlist */
+		if (tlist == NIL)
+			tlist = build_path_tlist(root, &best_path->path);
+
+		/*
+		 * Extract the relevant restriction clauses from the parent relation.
+		 * The executor must apply all these restrictions during the scan,
+		 * except for pseudoconstants which we'll take care of below.
+		 */
+		clauses = rel->baserestrictinfo;
+
+		/*
+		 * If this is a parameterized scan, we also need to enforce all
+		 * the join clauses available from the outer relation(s).
+		 */
+		if (best_path->path.param_info)
+			clauses = list_concat(list_copy(clauses),
+								  best_path->path.param_info->ppi_clauses);
+
+		/* Sort clauses into best execution order */
+		clauses = order_qual_clauses(root, clauses);
+
+		/*
+		 * Replace outer-relation variables with nestloop params.
+		 * Note that any other clauses which is managed by extension
+		 * itself has to be handled in InitCustomPlan() method, as
+		 * built-in code doing.
+		 */
+		if (best_path->path.param_info)
+			clauses = (List *)replace_nestloop_params(root, (Node *)clauses);
+	}
+	/*
+	 * Copy cost data from Path to Plan; no need to make custom-plan
+	 * providers do this
+	 */
+	copy_path_costsize((Plan *)custom_plan, (Path *)best_path);
+
+	/*
+	 * Let the custom-plan provider perform its final initialization
+	 * of this CustomPlan (to be an inherited type, actually) node
+	 * according to its own necessity.
+	 * Note that custom-plan provider may/can replace (or stack another
+	 * one on) its own custom-plan node on demand, for example, to add
+	 * Result node to handle pseudo constant using create_gating_plan().
+	 */
+	return custom_plan->methods->InitCustomPlan(custom_plan,
+												root, best_path,
+												tlist, clauses);
+}
 
 /*****************************************************************************
  *
@@ -2540,7 +2634,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 4d717df..c416859 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,14 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 			}
 			break;
 
+		case T_CustomPlan:
+			{
+				CustomPlan *cplan = (CustomPlan *) plan;
+
+				cplan->methods->SetCustomPlanRef(root, cplan, rtoffset);
+			}
+			break;
+
 		case T_NestLoop:
 		case T_MergeJoin:
 		case T_HashJoin:
@@ -1158,7 +1165,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..9833073 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -2283,6 +2283,30 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
 			context.paramids = bms_add_members(context.paramids, scan_params);
 			break;
 
+		case T_CustomPlan:
+			{
+				CustomPlan *cplan = (CustomPlan *) plan;
+
+				/*
+				 * If this custom-plan scab a particular relation, we
+				 * adjust paramids as other scan derivered node.
+				 */
+				if (cplan->scan.scanrelid > 0)
+					context.paramids = bms_add_members(context.paramids,
+													   scan_params);
+				/*
+				 * custom plan provider is responsible to apply
+				 * finalize_primnode() on the expression node of its
+				 * private fields, but no need to apply tlist and
+				 * qual of Plan node (already done above).
+				 */
+				if (cplan->methods->FinalizeCustomPlan)
+					cplan->methods->FinalizeCustomPlan(root, cplan,
+													   finalize_primnode,
+													   (void *)&context);
+			}
+			break;
+
 		case T_ModifyTable:
 			{
 				ModifyTable *mtplan = (ModifyTable *) plan;
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 319e8b2..b6b5dcb 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -16,6 +16,10 @@
 
 #include <math.h>
 
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "catalog/pg_custom_plan_provider.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
@@ -26,8 +30,11 @@
 #include "optimizer/restrictinfo.h"
 #include "optimizer/var.h"
 #include "parser/parsetree.h"
+#include "utils/inval.h"
 #include "utils/lsyscache.h"
+#include "utils/memutils.h"
 #include "utils/selfuncs.h"
+#include "utils/syscache.h"
 
 
 typedef enum
@@ -1926,3 +1933,106 @@ reparameterize_path(PlannerInfo *root, Path *path,
 	}
 	return NULL;
 }
+
+/*****************************************************************************
+ *		creation of custom-plan paths
+ *****************************************************************************/
+static List *custom_scan_callchain = NIL;
+static bool custom_plan_callchain_is_ready = false;
+static MemoryContext custom_plan_memcxt = NULL;
+
+static void
+invalidate_custom_plan_callchain(Datum arg, int cacheid, uint32 hashvalue)
+{
+	MemoryContextReset(custom_plan_memcxt);
+	custom_plan_callchain_is_ready = false;
+	custom_scan_callchain = NIL;
+}
+
+static void
+setup_custom_plan_callchain(void)
+{
+	Relation		rel;
+	SysScanDesc		scan;
+	HeapTuple		tuple;
+	MemoryContext	oldcxt;
+
+	custom_scan_callchain = NIL;
+
+	rel = heap_open(CustomPlanProviderRelationId, AccessShareLock);
+
+	/* full scan on the pg_custom_plan once */
+	scan = systable_beginscan(rel, InvalidOid, false, NULL, 0, NULL);
+
+	oldcxt = MemoryContextSwitchTo(custom_plan_memcxt);
+	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+	{
+		Form_pg_custom_plan_provider	cppForm
+			= (Form_pg_custom_plan_provider) GETSTRUCT(tuple);
+
+		if (cppForm->cppclass == CUSTOMPLAN_CLASS_SCAN)
+		{
+			custom_scan_callchain = lappend_oid(custom_scan_callchain,
+												cppForm->cpphandler);
+		}
+		else
+			elog(LOG, "Bug? custom-plan provider \"%s\" has unknown class: %c",
+				 NameStr(cppForm->cppname), cppForm->cppclass);
+	}
+	MemoryContextSwitchTo(oldcxt);
+	systable_endscan(scan);
+
+	heap_close(rel, AccessShareLock);
+
+	custom_plan_callchain_is_ready = true;
+}
+
+static void
+init_custom_plan_callchain(void)
+{
+	/* memory context to keep callchain for custom-plans */
+	custom_plan_memcxt = AllocSetContextCreate(CacheMemoryContext,
+											   "custom plan memory context",
+											   ALLOCSET_DEFAULT_MINSIZE,
+											   ALLOCSET_DEFAULT_INITSIZE,
+											   ALLOCSET_DEFAULT_MAXSIZE);
+
+	/* flush cached callchain on catalog updates */
+	CacheRegisterSyscacheCallback(CUSTOMPLANPROVIDEROID,
+								  invalidate_custom_plan_callchain,
+								  (Datum) 0);
+	/* also, initial setting up */
+	setup_custom_plan_callchain();
+}
+
+/*
+ * call_custom_scan_providers
+ *
+ * A callchain on relation scan. custom-plan provider can add alternative
+ * scan paths derived from CustomPath class.
+ */
+void
+call_custom_scan_providers(PlannerInfo *root,
+						   RelOptInfo *baserel,
+						   RangeTblEntry *rte)
+{
+	customScanArg	sarg;
+	ListCell	   *cell;
+
+	if (!custom_plan_memcxt)
+		init_custom_plan_callchain();
+	else if (!custom_plan_callchain_is_ready)
+		setup_custom_plan_callchain();
+
+	Assert(custom_plan_callchain_is_ready);
+	sarg.custom_class = CUSTOMPLAN_CLASS_SCAN;
+	sarg.root = root;
+	sarg.baserel = baserel;
+	sarg.rte = rte;
+
+	foreach (cell, custom_scan_callchain)
+	{
+		(void) OidFunctionCall1(lfirst_oid(cell),
+								PointerGetDatum(&sarg));
+	}
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a113809..c8ea278 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -51,6 +51,7 @@
 
 #include "catalog/index.h"
 #include "catalog/namespace.h"
+#include "catalog/pg_custom_plan_provider.h"
 #include "catalog/pg_trigger.h"
 #include "commands/defrem.h"
 #include "commands/trigger.h"
@@ -259,6 +260,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		DropOwnedStmt ReassignOwnedStmt
 		AlterTSConfigurationStmt AlterTSDictionaryStmt
 		CreateMatViewStmt RefreshMatViewStmt
+		CreateCustomPlanProviderStmt DropCustomPlanProviderStmt
 
 %type <node>	select_no_parens select_with_parens select_clause
 				simple_select values_clause
@@ -514,6 +516,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <str>		opt_existing_window_name
 %type <boolean> opt_if_not_exists
 
+%type <ival>	cpp_class
+%type <defelt>	cpp_option
+%type <list>	opt_cpp_options cpp_options
+
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
  * They must be listed first so that their numeric codes do not depend on
@@ -549,7 +555,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
 	CROSS CSV CURRENT_P
 	CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
-	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
+	CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CUSTOM CYCLE
 
 	DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
 	DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DESC
@@ -588,8 +594,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	OBJECT_P OF OFF OFFSET OIDS ON ONLY OPERATOR OPTION OPTIONS OR
 	ORDER ORDINALITY OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER
 
-	PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POSITION
-	PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
+	PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLAN PLANS POSITION
+	PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY PROVIDER
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROGRAM
 
 	QUOTE
@@ -599,8 +605,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK
 	ROW ROWS RULE
 
-	SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
-	SERIALIZABLE SERVER SESSION SESSION_USER SET SETOF SHARE
+	SAVEPOINT SCAN SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE
+	SEQUENCES SERIALIZABLE SERVER SESSION SESSION_USER SET SETOF SHARE
 	SHOW SIMILAR SIMPLE SMALLINT SNAPSHOT SOME STABLE STANDALONE_P START
 	STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBSTRING
 	SYMMETRIC SYSID SYSTEM_P
@@ -762,6 +768,7 @@ stmt :
 			| CreateAssertStmt
 			| CreateCastStmt
 			| CreateConversionStmt
+			| CreateCustomPlanProviderStmt
 			| CreateDomainStmt
 			| CreateExtensionStmt
 			| CreateFdwStmt
@@ -792,6 +799,7 @@ stmt :
 			| DoStmt
 			| DropAssertStmt
 			| DropCastStmt
+			| DropCustomPlanProviderStmt
 			| DropFdwStmt
 			| DropForeignServerStmt
 			| DropGroupStmt
@@ -8705,6 +8713,78 @@ CreateConversionStmt:
 			}
 		;
 
+/****************************************************************************
+ *
+ *	QUERY:
+ *			CREATE CUSTOM PLAN PROVIDER name FOR <class> <options>
+ *
+ ****************************************************************************/
+
+CreateCustomPlanProviderStmt:
+			CREATE CUSTOM PLAN PROVIDER name FOR cpp_class opt_cpp_options
+			{
+				CreateCustomPlanProviderStmt *n
+					= makeNode(CreateCustomPlanProviderStmt);
+				n->cpp_name = $5;
+				n->cpp_class = $7;
+				n->cpp_options = $8;
+				$$ = (Node *) n;
+			}
+		;
+
+cpp_class:
+			SCAN				{ $$ = CUSTOMPLAN_CLASS_SCAN; }
+		;
+
+cpp_option:
+			HANDLER handler_name
+			{
+				$$ = makeDefElem("handler", (Node *)$2);
+			}
+		;
+
+cpp_options:
+			cpp_option					{ $$ = list_make1($1); }
+			| cpp_options cpp_option	{ $$ = lappend($1, $2); }
+		;
+
+opt_cpp_options:
+			cpp_options			{ $$ = $1; }
+			| /* empty */		{ $$ = NIL; }
+		;
+
+/****************************************************************************
+ *
+ *     QUERY:
+ *             DROP CUSTOM PLAN PROVIDER name
+ *
+ ****************************************************************************/
+
+DropCustomPlanProviderStmt:
+			DROP CUSTOM PLAN PROVIDER name opt_drop_behavior
+			{
+				DropStmt *n = makeNode(DropStmt);
+				n->removeType = OBJECT_CPP;
+				n->objects = list_make1(list_make1(makeString($5)));
+				n->arguments = NIL;
+				n->missing_ok = false;
+				n->behavior = $6;
+				n->concurrent = false;
+				$$ = (Node *) n;
+			}
+		|	DROP CUSTOM PLAN PROVIDER IF_P EXISTS name opt_drop_behavior
+			{
+				DropStmt *n = makeNode(DropStmt);
+				n->removeType = OBJECT_CPP;
+				n->objects = list_make1(list_make1(makeString($7)));
+				n->arguments = NIL;
+				n->missing_ok = true;
+				n->behavior = $8;
+				n->concurrent = false;
+				$$ = (Node *) n;
+			}
+		;
+
 /*****************************************************************************
  *
  *		QUERY:
@@ -12914,6 +12994,7 @@ unreserved_keyword:
 			| CSV
 			| CURRENT_P
 			| CURSOR
+			| CUSTOM
 			| CYCLE
 			| DATA_P
 			| DATABASE
@@ -13025,6 +13106,7 @@ unreserved_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PLAN
 			| PLANS
 			| PRECEDING
 			| PREPARE
@@ -13035,6 +13117,7 @@ unreserved_keyword:
 			| PROCEDURAL
 			| PROCEDURE
 			| PROGRAM
+			| PROVIDER
 			| QUOTE
 			| RANGE
 			| READ
@@ -13060,6 +13143,7 @@ unreserved_keyword:
 			| ROWS
 			| RULE
 			| SAVEPOINT
+			| SCAN
 			| SCHEMA
 			| SCROLL
 			| SEARCH
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 07e0b98..cbfd3cf 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -202,6 +202,7 @@ check_xact_readonly(Node *parsetree)
 		case T_AlterTableSpaceOptionsStmt:
 		case T_AlterTableSpaceMoveStmt:
 		case T_CreateForeignTableStmt:
+		case T_CreateCustomPlanProviderStmt:
 		case T_ImportForeignSchemaStmt:
 		case T_SecLabelStmt:
 			PreventCommandIfReadOnly(CreateCommandTag(parsetree));
@@ -684,6 +685,14 @@ standard_ProcessUtility(Node *parsetree,
 			AlterEventTrigger((AlterEventTrigStmt *) parsetree);
 			break;
 
+		case  T_CreateCustomPlanProviderStmt:
+			{
+				CreateCustomPlanProviderStmt *cpp_stmt
+					= (CreateCustomPlanProviderStmt *)parsetree;
+				DefineCustomPlanProvider(cpp_stmt);
+			}
+			break;
+
 			/*
 			 * ******************************** ROLE statements ****
 			 */
@@ -1949,6 +1958,9 @@ CreateCommandTag(Node *parsetree)
 				case OBJECT_OPFAMILY:
 					tag = "DROP OPERATOR FAMILY";
 					break;
+				case OBJECT_CPP:
+					tag = "DROP CUSTOM PLAN PROVIDER";
+					break;
 				default:
 					tag = "???";
 			}
@@ -2216,6 +2228,10 @@ CreateCommandTag(Node *parsetree)
 			tag = "ALTER EVENT TRIGGER";
 			break;
 
+		case T_CreateCustomPlanProviderStmt:
+			tag = "CREATE CUSTOM PLAN PROVIDER";
+			break;
+
 		case T_CreatePLangStmt:
 			tag = "CREATE LANGUAGE";
 			break;
@@ -2840,6 +2856,10 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_CreateCustomPlanProviderStmt:
+			lev = LOGSTMT_DDL;
+			break;
+
 			/* already-planned queries */
 		case T_PlannedStmt:
 			{
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 7237e5d..3a949d6 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -5490,6 +5490,29 @@ 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-plan provider replaced a varno in expression tree by special
+ * varno.
+ */
+static Node *
+GetSpecialCustomVar(PlanState *ps, Var *varnode, PlanState **child_ps)
+{
+	CustomPlanState *cps = (CustomPlanState *) ps;
+
+	Assert(IsA(ps, CustomPlanState));
+	Assert(IS_SPECIAL_VARNO(varnode->varno));
+
+	if (!cps->methods->GetSpecialCustomVar)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("%s does not support special varno reference",
+						cps->methods->CustomName)));
+	return (Node *)cps->methods->GetSpecialCustomVar(cps, varnode, child_ps);
+}
 
 /*
  * Display a Var appropriately.
@@ -5519,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;
 
@@ -5543,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, CustomPlanState) &&
+			 (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 +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;
 
@@ -5831,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, CustomPlanState) &&
+			 (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/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 94d951c..1619de7 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -32,6 +32,7 @@
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_conversion.h"
+#include "catalog/pg_custom_plan_provider.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_db_role_setting.h"
 #include "catalog/pg_default_acl.h"
@@ -345,6 +346,28 @@ static const struct cachedesc cacheinfo[] = {
 		},
 		8
 	},
+	{CustomPlanProviderRelationId,	/* CUSTOMPLANPROVIDEROID */
+		CustomPlanProviderOidIndexId,
+		1,
+		{
+			ObjectIdAttributeNumber,
+			0,
+			0,
+			0
+		},
+		32
+	},
+	{CustomPlanProviderRelationId,	/* CUSTOMPLANPROVIDERNAME */
+		CustomPlanProviderNameIndexId,
+		1,
+		{
+			Anum_pg_custom_plan_provider_cppname,
+			0,
+			0,
+			0,
+		},
+		32
+	},
 	{DatabaseRelationId,		/* DATABASEOID */
 		DatabaseOidIndexId,
 		1,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 8ed2592..4a7186a 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -147,6 +147,7 @@ typedef enum ObjectClass
 	OCLASS_DEFACL,				/* pg_default_acl */
 	OCLASS_EXTENSION,			/* pg_extension */
 	OCLASS_EVENT_TRIGGER,		/* pg_event_trigger */
+	OCLASS_CPP,					/* pg_custom_plan_provider */
 	MAX_OCLASS					/* MUST BE LAST */
 } ObjectClass;
 
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 59576fd..b926e15 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -299,6 +299,12 @@ DECLARE_UNIQUE_INDEX(pg_extension_name_index, 3081, on pg_extension using btree(
 DECLARE_UNIQUE_INDEX(pg_range_rngtypid_index, 3542, on pg_range using btree(rngtypid oid_ops));
 #define RangeTypidIndexId					3542
 
+DECLARE_UNIQUE_INDEX(pg_custom_plan_provider_oid_index, 3563, on pg_custom_plan_provider using btree(oid oid_ops));
+#define CustomPlanProviderOidIndexId 3563
+
+DECLARE_UNIQUE_INDEX(pg_custom_plan_provider_name_index, 3564, on pg_custom_plan_provider using btree(cppname name_ops));
+#define CustomPlanProviderNameIndexId 3564
+
 /* last step of initialization script: build the indexes declared above */
 BUILD_INDICES
 
diff --git a/src/include/catalog/pg_custom_plan_provider.h b/src/include/catalog/pg_custom_plan_provider.h
new file mode 100644
index 0000000..f68b4de
--- /dev/null
+++ b/src/include/catalog/pg_custom_plan_provider.h
@@ -0,0 +1,50 @@
+/* -------------------------------------------------------------------------
+ *
+ * pg_custom_plan_provider.h
+ *	definition of the system "custom plan provider" relation
+ *  (pg_custom_plan_provider)
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * -------------------------------------------------------------------------
+ */
+#ifndef PG_CUSTOM_PLAN_PROVIDER_H
+#define PG_CUSTOM_PLAN_PROVIDER_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ *     pg_custom_plan_provider definition.  cpp turns this into
+ *     typedef struct FormData_pg_custom_plan_provider
+ * ----------------
+ */
+#define CustomPlanProviderRelationId       3562
+
+CATALOG(pg_custom_plan_provider,3562)
+{
+	NameData	cppname;		/* name of custom-plan provider */
+	char		cppclass;		/* class of custom-plan */
+	Oid			cpphandler;		/* function of custom-plan provider */
+} FormData_pg_custom_plan_provider;
+
+/* ----------------
+ *     Form_pg_custom_plan_provider corresponds to a pointer to a tuple
+ *     with the format of pg_custom_plan_provider relation.
+ * ----------------
+ */
+typedef FormData_pg_custom_plan_provider *Form_pg_custom_plan_provider;
+
+/* ----------------
+ *     compiler constants for pg_custom_plan_provider
+ * ----------------
+ */
+#define Natts_pg_custom_plan_provider			3
+#define Anum_pg_custom_plan_provider_cppname	1
+#define Anum_pg_custom_plan_provider_cppclass	2
+#define Anum_pg_custom_plan_provider_cpphandler	3
+
+/* definition of cppclass */
+#define CUSTOMPLAN_CLASS_SCAN			's'
+
+#endif	/* PG_CUSTOM_PLAN_PROVIDER_H */
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index f8b4a65..fc798a2 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");
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 0ebdbc1..b0f3f0c 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -130,6 +130,11 @@ extern Datum transformGenericOptions(Oid catalogId,
 						List *options,
 						Oid fdwvalidator);
 
+/* commands/custom_plan.c */
+extern Oid get_custom_plan_provider_oid(const char *cpp_name, bool missing_ok);
+extern void	RemoveCustomPlanProviderById(Oid cust_oid);
+extern Oid	DefineCustomPlanProvider(CreateCustomPlanProviderStmt *stmt);
+
 /* support routines in commands/define.c */
 
 extern char *defGetString(DefElem *def);
diff --git a/src/include/executor/nodeCustom.h b/src/include/executor/nodeCustom.h
new file mode 100644
index 0000000..abe1e94
--- /dev/null
+++ b/src/include/executor/nodeCustom.h
@@ -0,0 +1,30 @@
+/* ------------------------------------------------------------------------
+ *
+ * nodeCustom.h
+ *
+ * prototypes for CustomPlan 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 CustomPlanState *ExecInitCustomPlan(CustomPlan *cplan,
+										   EState *estate, int eflags);
+extern TupleTableSlot *ExecCustomPlan(CustomPlanState *node);
+extern Node *MultiExecCustomPlan(CustomPlanState *node);
+extern void ExecEndCustomPlan(CustomPlanState *node);
+
+extern void ExecReScanCustomPlan(CustomPlanState *node);
+extern void ExecCustomMarkPos(CustomPlanState *node);
+extern void ExecCustomRestrPos(CustomPlanState *node);
+
+#endif	/* NODECUSTOM_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index b271f21..26e6b33 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1504,6 +1504,48 @@ typedef struct ForeignScanState
 	void	   *fdw_state;		/* foreign-data wrapper can keep state here */
 } ForeignScanState;
 
+/* ----------------
+ * CustomPlanState information
+ *
+ *		CustomPlan nodes are used to execute custom code within executor.
+ * ----------------
+ */
+struct CustomExecMethods;
+struct ExplainState;	/* to avoid to include explain.h here */
+
+typedef struct CustomPlanState
+{
+	ScanState	ss;
+	const struct CustomExecMethods *methods;
+} CustomPlanState;
+
+typedef struct CustomExecMethods
+{
+	const char     *CustomName;
+
+	/* EXECUTOR methods */
+	void    (*BeginCustomPlan)(CustomPlanState *node,
+							   EState *estate,
+							   int eflags);
+	TupleTableSlot *(*ExecCustomPlan)(CustomPlanState *node);
+	Node   *(*MultiExecCustomPlan)(CustomPlanState *node);
+	void	(*EndCustomPlan)(CustomPlanState *node);
+	void	(*ReScanCustomPlan)(CustomPlanState *node);
+	void	(*MarkPosCustomPlan)(CustomPlanState *node);
+	void	(*RestrPosCustomPlan)(CustomPlanState *node);
+
+	/* EXPLAIN support */
+	void	(*ExplainCustomPlanTargetRel)(CustomPlanState *node,
+										  struct ExplainState *es);
+	void    (*ExplainCustomPlan)(CustomPlanState *node,
+								 List *ancestors,
+								 struct ExplainState *es);
+	void	(*ExplainCustomPreScanNode)(CustomPlanState *node,
+										Bitmapset **rels_used);
+	Node   *(*GetSpecialCustomVar)(CustomPlanState *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 067c768..d5fafbd 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -62,6 +62,8 @@ typedef enum NodeTag
 	T_CteScan,
 	T_WorkTableScan,
 	T_ForeignScan,
+	T_CustomPlan,
+	T_CustomPlanMarkPos,
 	T_Join,
 	T_NestLoop,
 	T_MergeJoin,
@@ -107,6 +109,7 @@ typedef enum NodeTag
 	T_CteScanState,
 	T_WorkTableScanState,
 	T_ForeignScanState,
+	T_CustomPlanState,
 	T_JoinState,
 	T_NestLoopState,
 	T_MergeJoinState,
@@ -224,6 +227,7 @@ typedef enum NodeTag
 	T_HashPath,
 	T_TidPath,
 	T_ForeignPath,
+	T_CustomPath,
 	T_AppendPath,
 	T_MergeAppendPath,
 	T_ResultPath,
@@ -366,6 +370,7 @@ typedef enum NodeTag
 	T_RefreshMatViewStmt,
 	T_ReplicaIdentityStmt,
 	T_AlterSystemStmt,
+	T_CreateCustomPlanProviderStmt,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8364bef..1f98cce 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1209,6 +1209,7 @@ typedef enum ObjectType
 	OBJECT_CONSTRAINT,
 	OBJECT_COLLATION,
 	OBJECT_CONVERSION,
+	OBJECT_CPP,
 	OBJECT_DATABASE,
 	OBJECT_DOMAIN,
 	OBJECT_EVENT_TRIGGER,
@@ -2073,6 +2074,18 @@ typedef struct AlterOpFamilyStmt
 } AlterOpFamilyStmt;
 
 /* ----------------------
+ *     Create Custom Plan Provider Statement
+ * ----------------------
+ */
+typedef struct CreateCustomPlanProviderStmt
+{
+	NodeTag     type;
+	char	   *cpp_name;		/* name of custom-plan provider */
+	char		cpp_class;		/* class of custom-plan provides */
+	List	   *cpp_options;	/* generic options for provider */
+} CreateCustomPlanProviderStmt;
+
+/* ----------------------
  *		Drop Table|Sequence|View|Index|Type|Domain|Conversion|Schema Statement
  * ----------------------
  */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 3b9c683..b48c735 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -15,6 +15,7 @@
 #define PLANNODES_H
 
 #include "access/sdir.h"
+#include "lib/stringinfo.h"
 #include "nodes/bitmapset.h"
 #include "nodes/primnodes.h"
 
@@ -479,6 +480,42 @@ typedef struct ForeignScan
 	bool		fsSystemCol;	/* true if any "system column" is needed */
 } ForeignScan;
 
+/* ----------------
+ *     CustomPlan node
+ * ----------------
+ */
+struct CustomPlanMethods;
+
+struct CustomPath;			/* to avoid to include nodes/relation.h here */
+struct PlannerInfo;			/* to avoid to include nodes/relation.h here */
+
+typedef struct CustomPlan
+{
+	Scan		scan;
+	const struct CustomPlanMethods *methods;
+} CustomPlan;
+
+typedef struct CustomPlanMethods
+{
+	const char *CustomName;
+	Plan	  *(*InitCustomPlan)(CustomPlan *custom_plan,
+								 struct PlannerInfo *root,
+								 struct CustomPath *best_path,
+								 List *tlist,
+								 List *clauses);
+	void	   (*SetCustomPlanRef)(struct PlannerInfo *root,
+								   CustomPlan *custom_plan,
+								   int rtoffset);
+	bool	   (*SupportBackwardScan)(CustomPlan *custom_plan);
+	void	   (*FinalizeCustomPlan)(struct PlannerInfo *root,
+									 CustomPlan *custom_plan,
+									 bool (*finalize_primnode)(),
+									 void *finalize_context);
+	Node	  *(*CreateCustomPlanState)(CustomPlan *custom_plan);
+	void	   (*TextOutCustomPlan)(StringInfo str,
+									const CustomPlan *node);
+	CustomPlan *(*CopyCustomPlan)(const CustomPlan *from);
+} CustomPlanMethods;
 
 /*
  * ==========
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index dacbe9c..a65bb0c 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"
@@ -881,6 +882,31 @@ 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;
+
+typedef struct CustomPath
+{
+	Path        path;
+	const struct CustomPathMethods *methods;
+} CustomPath;
+
+typedef struct CustomPathMethods
+{
+	const char *CustomName;
+	Node   *(*CreateCustomPlan)(PlannerInfo *root,
+								CustomPath *best_path);
+	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..df4d6e8 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -129,6 +129,20 @@ extern Path *reparameterize_path(PlannerInfo *root, Path *path,
 					double loop_count);
 
 /*
+ * interface towards custom-plan provider functions
+ */
+typedef struct {
+	uint32			custom_class;
+	PlannerInfo	   *root;
+	RelOptInfo	   *baserel;
+	RangeTblEntry  *rte;
+} customScanArg;
+
+extern void call_custom_scan_providers(PlannerInfo *root,
+									   RelOptInfo *baserel,
+									   RangeTblEntry *rte);
+
+/*
  * prototypes for relnode.c
  */
 extern void setup_simple_rel_arrays(PlannerInfo *root);
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 4504250..fbf1a0b 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,7 @@ 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 set_opfuncid(OpExpr *opexpr);
 extern void set_sa_opfuncid(ScalarArrayOpExpr *opexpr);
 extern void record_plan_function_dependency(PlannerInfo *root, Oid funcid);
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index b52e507..05603e5 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -107,6 +107,7 @@ PG_KEYWORD("current_time", CURRENT_TIME, RESERVED_KEYWORD)
 PG_KEYWORD("current_timestamp", CURRENT_TIMESTAMP, RESERVED_KEYWORD)
 PG_KEYWORD("current_user", CURRENT_USER, RESERVED_KEYWORD)
 PG_KEYWORD("cursor", CURSOR, UNRESERVED_KEYWORD)
+PG_KEYWORD("custom", CUSTOM, UNRESERVED_KEYWORD)
 PG_KEYWORD("cycle", CYCLE, UNRESERVED_KEYWORD)
 PG_KEYWORD("data", DATA_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("database", DATABASE, UNRESERVED_KEYWORD)
@@ -282,6 +283,7 @@ PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD)
 PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD)
+PG_KEYWORD("plan", PLAN, UNRESERVED_KEYWORD)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD)
 PG_KEYWORD("position", POSITION, COL_NAME_KEYWORD)
 PG_KEYWORD("preceding", PRECEDING, UNRESERVED_KEYWORD)
@@ -295,6 +297,7 @@ PG_KEYWORD("privileges", PRIVILEGES, UNRESERVED_KEYWORD)
 PG_KEYWORD("procedural", PROCEDURAL, UNRESERVED_KEYWORD)
 PG_KEYWORD("procedure", PROCEDURE, UNRESERVED_KEYWORD)
 PG_KEYWORD("program", PROGRAM, UNRESERVED_KEYWORD)
+PG_KEYWORD("provider", PROVIDER, UNRESERVED_KEYWORD)
 PG_KEYWORD("quote", QUOTE, UNRESERVED_KEYWORD)
 PG_KEYWORD("range", RANGE, UNRESERVED_KEYWORD)
 PG_KEYWORD("read", READ, UNRESERVED_KEYWORD)
@@ -325,6 +328,7 @@ PG_KEYWORD("row", ROW, COL_NAME_KEYWORD)
 PG_KEYWORD("rows", ROWS, UNRESERVED_KEYWORD)
 PG_KEYWORD("rule", RULE, UNRESERVED_KEYWORD)
 PG_KEYWORD("savepoint", SAVEPOINT, UNRESERVED_KEYWORD)
+PG_KEYWORD("scan", SCAN, UNRESERVED_KEYWORD)
 PG_KEYWORD("schema", SCHEMA, UNRESERVED_KEYWORD)
 PG_KEYWORD("scroll", SCROLL, UNRESERVED_KEYWORD)
 PG_KEYWORD("search", SEARCH, UNRESERVED_KEYWORD)
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index f97229f..e1e56f7 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -52,6 +52,8 @@ enum SysCacheIdentifier
 	CONNAMENSP,
 	CONSTROID,
 	CONVOID,
+	CUSTOMPLANPROVIDEROID,
+	CUSTOMPLANPROVIDERNAME,
 	DATABASEOID,
 	DEFACLROLENSPOBJ,
 	ENUMOID,
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 111d24c..5ebceea 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -97,6 +97,7 @@ pg_class|t
 pg_collation|t
 pg_constraint|t
 pg_conversion|t
+pg_custom_plan_provider|t
 pg_database|t
 pg_db_role_setting|t
 pg_default_acl|t
#19Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#14)

On Thu, Jul 17, 2014 at 3:38 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Alvaro Herrera <alvherre@2ndquadrant.com> writes:

I haven't followed this at all, but I just skimmed over it and noticed
the CustomPlanMarkPos thingy; apologies if this has been discussed
before. It seems a bit odd to me; why isn't it sufficient to have a
boolean flag in regular CustomPlan to indicate that it supports
mark/restore?

Yeah, I thought that was pretty bogus too, but it's well down the
list of issues that were there last time I looked at this ...

I think the threshold question for this incarnation of the patch is
whether we're happy with new DDL (viz, CREATE CUSTOM PLAN PROVIDER) as
a way of installing new plan providers into the database. If we are,
then I can go ahead and enumerate a long list of things that will need
to be fixed to make that code acceptable (such as adding pg_dump
support). But if we're not, there's no point in spending any time on
that part of the patch.

I can see a couple of good reasons to think that this approach might
be reasonable:

- In some ways, a custom plan provider (really, at this point, a
custom scan provider) is very similar to a foreign data wrapper. To
the guts of PostgreSQL, an FDW is a sort of black box that knows how
to scan some data not managed by PostgreSQL. A custom plan provider
is similar, except that it scans data that *is* managed by PostgreSQL.

- There's also some passing resemblance between a custom plan provider
and an access method. Access methods provide a set of APIs for fast
access to data via an index, while custom plan providers provide an
API for fast access to data via magic that the core system doesn't
(and need not) understand. While access methods don't have associated
SQL syntax, they do include catalog structure, so perhaps this should
too, by analogy.

All that having been said, I'm having a hard time mustering up
enthusiasm for this way of doing things. As currently constituted,
the pg_custom_plan_provider catalog contains only a name, a "class"
that is always 's' for scan, and a handler function OID. Quite
frankly, that's a whole lot of nothing. If we got rid of the
pg_catalog structure and just had something like
RegisterCustomPlanProvider(char *name, void (*)(customScanArg *),
which could be invoked from _PG_init(), hundreds and hundreds of lines
of code could go away and we wouldn't lose any actual functionality;
you'd just list your custom plan providers in shared_preload_libraries
or local_preload_libraries instead of listing them in a system
catalog. In fact, you might even have more functionality, because you
could load providers into particular sessions rather than system-wide,
which isn't possible with this design.

I think the underlying issue here really has to do with when custom
plan providers get invoked - what triggers that? For foreign data
wrappers, we have some relations that are plain tables (relkind = 'r')
and no foreign data wrapper code is invoked. We have others that are
flagged as foreign tables (relkind = 'f') and for those we look up the
matching FDW (via ftserver) and run the code. Similarly, for an index
AM, we notice that the relation is an index (relkind = 'r') and then
consult relam to figure out which index AM we should invoke. But as
KaiGai is conceiving this feature, it's quite different. Rather than
applying only to particular relations, and being mutually exclusive
with other options that might apply to those relations, it applies to
*everything* in the database in addition to whatever other options may
be present. The included ctidscan implementation is just as good an
example as PG-Strom: you inspect the query and see, based on the
operators present, whether there's any hope of accelerating things.
In other words, there's no user configuration - and also, not
irrelevantly, no persistent on-disk state the way you have for an
index, or even an FDW, which has on disk state to the extent that
there have to be catalog entries tying a particular FDW to a
particular table.

A lot of the previous discussion of this topic revolves around the
question of whether we can unify the use case that this patch is
targeting with other things - e.g. Citus's desire to store its data
files within the data directory while retaining control over data
access, thus not a perfect fit for FDWs; the desire to push joins down
to foreign servers; more generally, the desire to replace a join with
a custom plan that may or may not use access paths for the underlying
relations as subpaths. I confess I'm not seeing a whole lot of
commonality with anything other than the custom-join-path idea, which
probably shares many of what I believe to be the relevant
characteristics of a custom scan as conceived by KaiGai: namely, that
all of the decisions about whether to inject a custom path in
particular circumstances are left up to the provider itself based on
inspection of the specific query, rather than being a result of user
configuration.

So, I'm tentatively in favor of stripping the DDL support out of this
patch and otherwise trying to boil it down to something that's really
minimal, but I'd like to hear what others think.

--
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

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

Robert Haas <robertmhaas@gmail.com> writes:

I think the threshold question for this incarnation of the patch is
whether we're happy with new DDL (viz, CREATE CUSTOM PLAN PROVIDER) as
a way of installing new plan providers into the database.

I tend to agree with your conclusion that that's a whole lot of
infrastructure with very little return. I don't see anything here
we shouldn't do via function hooks instead, and/or a "register" callback
from a dynamically loaded library.

Also, we tend to think (for good reason) that once something is embedded
at the SQL level it's frozen; we are much more willing to redesign C-level
APIs. There is no possible way that it's a good idea for this stuff to
get frozen in its first iteration.

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

#21Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Robert Haas (#19)

2014-08-23 0:39 GMT+09:00 Robert Haas <robertmhaas@gmail.com>:

On Thu, Jul 17, 2014 at 3:38 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Alvaro Herrera <alvherre@2ndquadrant.com> writes:

I haven't followed this at all, but I just skimmed over it and noticed
the CustomPlanMarkPos thingy; apologies if this has been discussed
before. It seems a bit odd to me; why isn't it sufficient to have a
boolean flag in regular CustomPlan to indicate that it supports
mark/restore?

Yeah, I thought that was pretty bogus too, but it's well down the
list of issues that were there last time I looked at this ...

I think the threshold question for this incarnation of the patch is
whether we're happy with new DDL (viz, CREATE CUSTOM PLAN PROVIDER) as
a way of installing new plan providers into the database. If we are,
then I can go ahead and enumerate a long list of things that will need
to be fixed to make that code acceptable (such as adding pg_dump
support). But if we're not, there's no point in spending any time on
that part of the patch.

Even though I'm patch author, I'd like to agree with this approach.
In fact, the previous custom-plan interface I proposed to the v9.4
development cycle does not include DDL support to register the
custom-plan providers, however, it works fine.

One thing I was pointed out, it is the reason why I implemented
DDL support, is that intermediation of c-language function also
loads the extension module implicitly. It is an advantage, but
not sure whether it shall be supported from the beginning.

I can see a couple of good reasons to think that this approach might
be reasonable:

- In some ways, a custom plan provider (really, at this point, a
custom scan provider) is very similar to a foreign data wrapper. To
the guts of PostgreSQL, an FDW is a sort of black box that knows how
to scan some data not managed by PostgreSQL. A custom plan provider
is similar, except that it scans data that *is* managed by PostgreSQL.

- There's also some passing resemblance between a custom plan provider
and an access method. Access methods provide a set of APIs for fast
access to data via an index, while custom plan providers provide an
API for fast access to data via magic that the core system doesn't
(and need not) understand. While access methods don't have associated
SQL syntax, they do include catalog structure, so perhaps this should
too, by analogy.

All that having been said, I'm having a hard time mustering up
enthusiasm for this way of doing things. As currently constituted,
the pg_custom_plan_provider catalog contains only a name, a "class"
that is always 's' for scan, and a handler function OID. Quite
frankly, that's a whole lot of nothing. If we got rid of the
pg_catalog structure and just had something like
RegisterCustomPlanProvider(char *name, void (*)(customScanArg *),
which could be invoked from _PG_init(), hundreds and hundreds of lines
of code could go away and we wouldn't lose any actual functionality;
you'd just list your custom plan providers in shared_preload_libraries
or local_preload_libraries instead of listing them in a system
catalog. In fact, you might even have more functionality, because you
could load providers into particular sessions rather than system-wide,
which isn't possible with this design.

Indeed. It's an advantage of the approach without system catalog.

I think the underlying issue here really has to do with when custom
plan providers get invoked - what triggers that? For foreign data
wrappers, we have some relations that are plain tables (relkind = 'r')
and no foreign data wrapper code is invoked. We have others that are
flagged as foreign tables (relkind = 'f') and for those we look up the
matching FDW (via ftserver) and run the code. Similarly, for an index
AM, we notice that the relation is an index (relkind = 'r') and then
consult relam to figure out which index AM we should invoke. But as
KaiGai is conceiving this feature, it's quite different. Rather than
applying only to particular relations, and being mutually exclusive
with other options that might apply to those relations, it applies to
*everything* in the database in addition to whatever other options may
be present. The included ctidscan implementation is just as good an
example as PG-Strom: you inspect the query and see, based on the
operators present, whether there's any hope of accelerating things.
In other words, there's no user configuration - and also, not
irrelevantly, no persistent on-disk state the way you have for an
index, or even an FDW, which has on disk state to the extent that
there have to be catalog entries tying a particular FDW to a
particular table.

Yes, that's my point. In case of FDW or index AM, the query planner
can have some expectations how relevant executor node will handle
the given relation scan according to the persistent state.
However, custom-plan is a black-box to the query planner, and it
cannot have expectation of how relation scan is handled, except for
the cost value estimated by custom-plan provider.
Thus, this interface is designed to invoke custom-plan providers being
registered on relation scan, to ask them whether it can offer alternative
way to scan.

Probably, it may have a shortcut to skip invocation in case when custom-
plan provider obviously cannot provide any alternative plan.
For example, we may add a flag to RegisterCustomPlanProvider() to
tell this custom-plan provider works on only relkind='r', thus no need to
invoke on other relation types.

A lot of the previous discussion of this topic revolves around the
question of whether we can unify the use case that this patch is
targeting with other things - e.g. Citus's desire to store its data
files within the data directory while retaining control over data
access, thus not a perfect fit for FDWs; the desire to push joins down
to foreign servers; more generally, the desire to replace a join with
a custom plan that may or may not use access paths for the underlying
relations as subpaths. I confess I'm not seeing a whole lot of
commonality with anything other than the custom-join-path idea, which
probably shares many of what I believe to be the relevant
characteristics of a custom scan as conceived by KaiGai: namely, that
all of the decisions about whether to inject a custom path in
particular circumstances are left up to the provider itself based on
inspection of the specific query, rather than being a result of user
configuration.

So, I'm tentatively in favor of stripping the DDL support out of this
patch and otherwise trying to boil it down to something that's really
minimal, but I'd like to hear what others think.

I'd like to follow this direction, and start stripping the DDL support.

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

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

On Fri, Aug 22, 2014 at 9:48 PM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

One thing I was pointed out, it is the reason why I implemented
DDL support, is that intermediation of c-language function also
loads the extension module implicitly. It is an advantage, but
not sure whether it shall be supported from the beginning.

That is definitely an advantage of the DDL-based approach, but I think
it's too much extra machinery for not enough real advantage. Sounds
like we all agree, so ...

I'd like to follow this direction, and start stripping the DDL support.

...please make it so.

--
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

#23Kouhei Kaigai
kaigai@ak.jp.nec.com
In reply to: Robert Haas (#22)
1 attachment(s)

I'd like to follow this direction, and start stripping the DDL support.

...please make it so.

The attached patch eliminates DDL support.

Instead of the new CREATE CUSTOM PLAN PROVIDER statement,
it adds an internal function; register_custom_scan_provider
that takes custom plan provider name and callback function
to add alternative scan path (should have a form of CustomPath)
during the query planner is finding out the cheapest path to
scan the target relation.
Also, documentation stuff is revised according to the latest
design.
Any other stuff keeps the previous design.

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

Show quoted text

-----Original Message-----
From: pgsql-hackers-owner@postgresql.org
[mailto:pgsql-hackers-owner@postgresql.org] On Behalf Of Robert Haas
Sent: Tuesday, August 26, 2014 1:09 PM
To: Kohei KaiGai
Cc: Tom Lane; Alvaro Herrera; Kaigai Kouhei(海外 浩平); Shigeru Hanada;
Simon Riggs; Stephen Frost; Andres Freund; PgHacker; Jim Mlodgenski; Peter
Eisentraut
Subject: Re: [HACKERS] [v9.5] Custom Plan API

On Fri, Aug 22, 2014 at 9:48 PM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

One thing I was pointed out, it is the reason why I implemented DDL
support, is that intermediation of c-language function also loads the
extension module implicitly. It is an advantage, but not sure whether
it shall be supported from the beginning.

That is definitely an advantage of the DDL-based approach, but I think it's
too much extra machinery for not enough real advantage. Sounds like we
all agree, so ...

I'd like to follow this direction, and start stripping the DDL support.

...please make it so.

--
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

Attachments:

pgsql-v9.5-custom-plan.v8.patchapplication/octet-stream; name=pgsql-v9.5-custom-plan.v8.patchDownload
 contrib/Makefile                        |    1 +
 contrib/ctidscan/Makefile               |   18 +
 contrib/ctidscan/ctidscan.c             |  935 +++++++++++++++++++++++++++++++
 contrib/ctidscan/ctidscan.control       |    5 +
 contrib/ctidscan/expected/ctidscan.out  |  312 ++++++++++
 contrib/ctidscan/sql/ctidscan.sql       |   54 ++
 doc/src/sgml/custom-plan-provider.sgml  |  385 +++++++++++++
 doc/src/sgml/filelist.sgml              |    1 +
 doc/src/sgml/postgres.sgml              |    1 +
 src/backend/commands/explain.c          |   39 ++
 src/backend/executor/Makefile           |    2 +-
 src/backend/executor/execAmi.c          |   39 ++-
 src/backend/executor/execProcnode.c     |   18 +
 src/backend/executor/nodeCustom.c       |  147 +++++
 src/backend/nodes/copyfuncs.c           |   18 +
 src/backend/nodes/outfuncs.c            |   27 +
 src/backend/optimizer/path/allpaths.c   |   30 +-
 src/backend/optimizer/path/costsize.c   |    2 +-
 src/backend/optimizer/plan/createplan.c |   97 ++++-
 src/backend/optimizer/plan/setrefs.c    |   11 +-
 src/backend/optimizer/plan/subselect.c  |   24 +
 src/backend/optimizer/util/pathnode.c   |   79 +++
 src/backend/utils/adt/ruleutils.c       |   73 +++
 src/include/catalog/pg_operator.h       |    3 +
 src/include/executor/executor.h         |    3 +-
 src/include/executor/nodeCustom.h       |   30 +
 src/include/nodes/execnodes.h           |   44 ++
 src/include/nodes/nodes.h               |    3 +
 src/include/nodes/plannodes.h           |   36 ++
 src/include/nodes/relation.h            |   30 +
 src/include/optimizer/pathnode.h        |   18 +
 src/include/optimizer/planmain.h        |    2 +
 32 files changed, 2454 insertions(+), 33 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..4319a84
--- /dev/null
+++ b/contrib/ctidscan/ctidscan.c
@@ -0,0 +1,935 @@
+/*
+ * 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 {
+	CustomPlan		cplan;
+	List		   *ctid_quals;
+} CtidScanPlan;
+
+typedef struct {
+	CustomPlanState	cps;
+	List		   *ctid_quals;		/* list of ExprState for inequality ops */
+} CtidScanState;
+
+static CustomPathMethods	ctidscan_path_methods;
+static CustomPlanMethods	ctidscan_plan_methods;
+static CustomExecMethods	ctidscan_exec_methods;
+
+/* function declarations */
+void	_PG_init(void);
+
+#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;
+}
+
+/*
+ * CreateCtidScanPlan - A method of CustomPath; that populate a custom
+ * object being delivered from CustomPlan type, according to the supplied
+ * CustomPath object.
+ */
+static Node *
+CreateCtidScanPlan(PlannerInfo *root, CustomPath *best_path)
+{
+	CtidScanPath   *ctid_path = (CtidScanPath *) best_path;
+	CtidScanPlan   *ctid_scan;
+
+	ctid_scan = palloc0(sizeof(CtidScanPlan));
+	NodeSetTag(ctid_scan, T_CustomPlan);
+	ctid_scan->cplan.cpp_flags = best_path->cpp_flags;
+	ctid_scan->cplan.methods = &ctidscan_plan_methods;
+	ctid_scan->ctid_quals = ctid_path->ctid_quals;
+
+	return (Node *)&ctid_scan->cplan;
+}
+
+/*
+ * InitCtidScanPlan
+ *
+ * main portion to initialize CustomPlan node.
+ */
+static Plan *
+InitCtidScanPlan(CustomPlan *custom_plan,
+				 PlannerInfo *root, CustomPath *best_path,
+				 List *tlist, List *clauses)
+{
+	CtidScanPlan   *ctid_scan = (CtidScanPlan *)custom_plan;
+	List		   *ctid_quals = ((CtidScanPath *)best_path)->ctid_quals;
+
+	Assert(ctid_scan->cplan.scan.scanrelid > 0);
+
+	/* Set targetlist as is */
+	ctid_scan->cplan.scan.plan.targetlist = tlist;
+	/* Reduce RestrictInfo list to bare expressions */
+	ctid_scan->cplan.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 &ctid_scan->cplan.scan.plan;
+}
+
+/*
+ * 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 CustomPlan; 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,
+				   CustomPlan *custom_plan,
+				   int rtoffset)
+{
+	CtidScanPlan   *ctidscan = (CtidScanPlan *) custom_plan;
+	Scan		   *scan = &ctidscan->cplan.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 CustomPlan; that handles callbacks
+ * by finalize_plan().
+ */
+static void
+FinalizeCtidScanPlan(PlannerInfo *root,
+					 CustomPlan *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 CustomPlan; that populate a custom
+ * object being delivered from CustomPlanState type, according to the
+ * supplied CustomPath object.
+ */
+static Node *
+CreateCtidScanState(CustomPlan *custom_plan)
+{
+	CtidScanState  *ctss = palloc0(sizeof(CtidScanState));
+
+	NodeSetTag(ctss, T_CustomPlanState);
+	ctss->cps.cpp_flags = custom_plan->cpp_flags;
+	ctss->cps.methods = &ctidscan_exec_methods;
+
+	return (Node *)&ctss->cps;
+}
+
+/*
+ * TextOutCtidScanPlan - A method of CustomPlan; that generates text
+ * representation of the given object.
+ */
+static void
+TextOutCtidScanPlan(StringInfo str, const CustomPlan *node)
+{
+	CtidScanPlan   *ctid_plan = (CtidScanPlan *) node;
+
+	appendStringInfo(str, " :ctid_quals %s",
+					 nodeToString(ctid_plan->ctid_quals));
+}
+
+/*
+ * CopyCtidScanPlan - A method of CustomPlan; that create a copied object.
+ */
+static CustomPlan *
+CopyCtidScanPlan(const CustomPlan *from)
+{
+	CtidScanPlan   *oldnode = (CtidScanPlan *) from;
+	CtidScanPlan   *newnode = palloc0(sizeof(CtidScanPlan));
+
+	NodeSetTag(newnode, T_CustomPlan);
+	newnode->cplan.methods = oldnode->cplan.methods;
+	newnode->ctid_quals = copyObject(oldnode->ctid_quals);
+
+	return &newnode->cplan;
+}
+
+/*
+ * BeginCtidScan - A method of CustomPlanState; that initializes
+ * the supplied CtidScanState object, at beginning of the executor.
+ */
+static void
+BeginCtidScan(CustomPlanState *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->cps.ss.ss_currentScanDesc = NULL;
+}
+
+/*
+ * ReScanCtidScan - A method of CustomPlanState; that rewind the current
+ * seek position.
+ */
+static void
+ReScanCtidScan(CustomPlanState *node)
+{
+	CtidScanState  *ctss = (CtidScanState *)node;
+	HeapScanDesc	scan = ctss->cps.ss.ss_currentScanDesc;
+	EState		   *estate = node->ss.ps.state;
+	ScanDirection	direction = estate->es_direction;
+	Relation		relation = ctss->cps.ss.ss_currentRelation;
+	ExprContext	   *econtext = ctss->cps.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->cps.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->cps.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->cps.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(CustomPlanState *node)
+{
+	CtidScanState  *ctss = (CtidScanState *) node;
+	HeapScanDesc	scan;
+	TupleTableSlot *slot;
+	EState		   *estate = node->ss.ps.state;
+	ScanDirection	direction = estate->es_direction;
+	HeapTuple		tuple;
+
+	if (!ctss->cps.ss.ss_currentScanDesc)
+		ReScanCtidScan(node);
+	scan = ctss->cps.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->cps.ss.ss_ScanTupleSlot;
+	ExecStoreTuple(tuple, slot, scan->rs_cbuf, false);
+
+	return slot;
+}
+
+static bool
+CTidRecheckCustomScan(CustomPlanState *node, TupleTableSlot *slot)
+{
+	return true;
+}
+
+/*
+ * ExecCtidScan - A method of CustomPlanState; that fetches a tuple
+ * from the relation, if exist anymore.
+ */
+static TupleTableSlot *
+ExecCtidScan(CustomPlanState *node)
+{
+	return ExecScan(&node->ss,
+					(ExecScanAccessMtd) CTidAccessCustomScan,
+					(ExecScanRecheckMtd) CTidRecheckCustomScan);
+}
+
+/*
+ * CTidEndCustomScan - A method of CustomPlanState; that closes heap and
+ * scan descriptor, and release other related resources.
+ */
+static void
+EndCtidScan(CustomPlanState *node)
+{
+	CtidScanState  *ctss = (CtidScanState *)node;
+
+	if (ctss->cps.ss.ss_currentScanDesc)
+		heap_endscan(ctss->cps.ss.ss_currentScanDesc);
+}
+
+/*
+ * ExplanCtidScanTargetRel - A method of CustomPlanState; that output
+ * relation's name to be scanned.
+ */
+static void
+ExplanCtidScanTargetRel(CustomPlanState *node, ExplainState *es)
+{
+	CtidScanState  *ctss = (CtidScanState *) node;
+	CtidScanPlan   *ctid_plan = (CtidScanPlan *) ctss->cps.ss.ps.plan;
+	Index			rti = ctid_plan->cplan.scan.scanrelid;
+	RangeTblEntry   *rte;
+	char		   *objectname = NULL;
+	char		   *namespace = NULL;
+	char		   *refname;
+
+	/* logic copied from ExplainTargetRel */
+	rte = rt_fetch(rti, es->rtable);
+	refname = (char *) list_nth(es->rtable_names, rti - 1);
+	if (refname == NULL)
+		refname = rte->eref->aliasname;
+
+	Assert(rte->rtekind == RTE_RELATION);
+	objectname = get_rel_name(rte->relid);
+	if (es->verbose)
+		namespace = get_namespace_name(get_rel_namespace(rte->relid));
+
+	if (es->format == EXPLAIN_FORMAT_TEXT)
+	{
+		appendStringInfoString(es->str, " on");
+		if (namespace != NULL)
+			appendStringInfo(es->str, " %s.%s", quote_identifier(namespace),
+							 quote_identifier(objectname));
+		else if (objectname != NULL)
+			appendStringInfo(es->str, " %s", quote_identifier(objectname));
+		if (objectname == NULL || strcmp(refname, objectname) != 0)
+			appendStringInfo(es->str, " %s", quote_identifier(refname));
+	}
+	else
+	{
+		if (objectname != NULL)
+			ExplainPropertyText("Relation Name", objectname, es);
+		if (namespace != NULL)
+			ExplainPropertyText("Schema", namespace, es);
+		ExplainPropertyText("Alias", refname, es);
+	}
+}
+
+/*
+ * ExplainCtidScan - A method of CustomPlanState; that shows extra info
+ * on EXPLAIN command.
+ */
+static void
+ExplainCtidScan(CustomPlanState *node, List *ancestors, ExplainState *es)
+{
+	CtidScanState  *ctss = (CtidScanState *) node;
+	CtidScanPlan   *ctid_plan = (CtidScanPlan *) ctss->cps.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);
+	}
+}
+
+/*
+ * ExplainCtidPreScanNode - A method of CustomPlanState; that informs
+ * the core backend relation's rtindex to be referenced, prior to the
+ * main EXPLAIN processing.
+ */
+static void
+ExplainCtidPreScanNode(CustomPlanState *node, Bitmapset **rels_used)
+{
+	CtidScanState  *ctss = (CtidScanState *) node;
+	Index			scanrelid = ((Scan *)ctss->cps.ss.ps.plan)->scanrelid;
+
+	*rels_used = bms_add_member(*rels_used, scanrelid);
+}
+
+/*
+ * Entrypoint of this extension
+ */
+static void
+CtidScanAddPath(customScanArg *cscan_arg)
+{
+	PlannerInfo	   *root	= cscan_arg->root;
+	RangeTblEntry  *rte		= cscan_arg->rte;
+	RelOptInfo	   *baserel	= cscan_arg->baserel;
+	char			relkind;
+	ListCell	   *lc;
+	List		   *ctid_quals = NIL;
+
+	/* all we can support is regular relations */
+	if (rte->rtekind != RTE_RELATION)
+		return;
+
+	relkind = get_rel_relkind(rte->relid);
+	if (relkind != RELKIND_RELATION &&
+		relkind != RELKIND_MATVIEW &&
+		relkind != RELKIND_TOASTVALUE)
+		return;
+
+	/* walk on the restrict info */
+	foreach (lc, baserel->baserestrictinfo)
+	{
+		RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+		List		 *temp;
+
+		if (!IsA(rinfo, RestrictInfo))
+			continue;		/* probably should never happen */
+		temp = CTidQualFromExpr((Node *) rinfo->clause, baserel->relid);
+		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_CustomPlan;
+		ctid_path->cpath.path.parent = baserel;
+		ctid_path->cpath.path.param_info
+			= get_baserel_parampathinfo(root, baserel, required_outer);
+		ctid_path->cpath.cpp_flags = CPP_FLAGS_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);
+	}
+}
+
+/*
+ * Entrypoint of this extension
+ */
+void
+_PG_init(void)
+{
+	/* setup ctidscan_path_methods */
+	ctidscan_path_methods.CustomName = "ctidscan";
+	ctidscan_path_methods.CreateCustomPlan = CreateCtidScanPlan;
+	ctidscan_path_methods.TextOutCustomPath = TextOutCtidScanPath;
+
+	/* setup ctidscan_plan_methods */
+	ctidscan_plan_methods.CustomName = "ctidscan";
+	ctidscan_plan_methods.InitCustomPlan = InitCtidScanPlan;
+	ctidscan_plan_methods.SetCustomPlanRef = SetCtidScanPlanRef;
+	ctidscan_plan_methods.FinalizeCustomPlan = FinalizeCtidScanPlan;
+	ctidscan_plan_methods.CreateCustomPlanState = CreateCtidScanState;
+	ctidscan_plan_methods.TextOutCustomPlan = TextOutCtidScanPlan;
+	ctidscan_plan_methods.CopyCustomPlan = CopyCtidScanPlan;
+
+	/* setup ctidscan_planstate_methods */
+	ctidscan_exec_methods.CustomName = "ctidscan";
+	ctidscan_exec_methods.BeginCustomPlan = BeginCtidScan;
+	ctidscan_exec_methods.ExecCustomPlan = ExecCtidScan;
+	ctidscan_exec_methods.EndCustomPlan = EndCtidScan;
+	ctidscan_exec_methods.ReScanCustomPlan = ReScanCtidScan;
+	ctidscan_exec_methods.MarkPosCustomPlan = NULL;
+	ctidscan_exec_methods.RestrPosCustomPlan = NULL;
+	ctidscan_exec_methods.ExplainCustomPlanTargetRel = ExplanCtidScanTargetRel;
+	ctidscan_exec_methods.ExplainCustomPlan = ExplainCtidScan;
+	ctidscan_exec_methods.ExplainCustomPreScanNode = ExplainCtidPreScanNode;
+	ctidscan_exec_methods.GetSpecialCustomVar = NULL;
+
+	/* registration of this custom-plan provider for table scanning */
+	register_custom_scan_provider("ctidscan", CtidScanAddPath);
+}
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..79cd8db
--- /dev/null
+++ b/contrib/ctidscan/expected/ctidscan.out
@@ -0,0 +1,312 @@
+--
+-- Regression Tests for Custom Plan APIs
+--
+LOAD '$libdir/ctidscan';
+-- 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                              
+----------------------------------------------------------------------
+ Custom (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 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 (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 (ctidscan) on t1
+               Filter: (ctid < '(2,10)'::tid)
+               ctid quals: (ctid < '(2,10)'::tid)
+   ->  Sort
+         Sort Key: t2.ctid
+         ->  Custom (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 (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 (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)
+
+-- 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..9ac6df8
--- /dev/null
+++ b/contrib/ctidscan/sql/ctidscan.sql
@@ -0,0 +1,54 @@
+--
+-- Regression Tests for Custom Plan APIs
+--
+
+LOAD '$libdir/ctidscan';
+
+-- 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;
+
+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);
+
+-- Test cleanup
+DROP SCHEMA regtest_custom_scan CASCADE;
diff --git a/doc/src/sgml/custom-plan-provider.sgml b/doc/src/sgml/custom-plan-provider.sgml
new file mode 100644
index 0000000..3143a4a
--- /dev/null
+++ b/doc/src/sgml/custom-plan-provider.sgml
@@ -0,0 +1,385 @@
+<!-- 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-plan node the custom-plan provider.
+ </para>
+
+ <sect1 id="cpp-overall-steps">
+  <title>Custom Plan Overall Steps</title>
+  <para>
+   A custom plan provider shall be registered using
+   <function>register_custom_scan_provider</> for relation scans.
+   Any other workloads, like relations joins, are not supported right now.
+   It takes argument of custom-plan provider name and a function pointer
+   of its entrypoint.
+   I usuall shall be installed using <function>_PG_init()</> of extension
+   when either of preload configuration or an explicit
+   <xref linkend="sql-load"> command loaded the extension of custom-plan
+   provider.
+  </para>
+  <para>
+   In case when custom-plan providers are registered to relation scan,
+   its entrypoint function shall be called back by the query planner to
+   check up availability and cost of the alternative execution path being
+   implemented by the custom-plan provider.
+   The entrypoint function for relation scan takes an argument of
+   <structname>customScanArg</> that tells extesions overall information of
+   relations to be scanned and the context of this scan.
+   If custom-plan provider can propose one or more alternative scan paths,
+   it constructs <structname>CustomPah</> object with estimated cost value
+   and a set of callbacks defined at <structname>CustomPathMethods</>
+   structure, then added using <function>add_path()</>.
+  </para>
+  <para>
+   Planner compares all the potential execution paths based on its cost.
+   Once a custom path that was added by custom plan provider gets chosen,
+   <structname>CreateCustomPlan</> callback shall be called, to populate
+   a <structname>CustomPlan</> (or inherited object type) node according
+   to the <structname>CustomPath</> node preliminary constructed.
+   In the similar manner, <structname>CreateCustomPlanState</> callback
+   shall be called, to populate a <structname>CustomPlanState</> (or
+   inherited object type) node according to the <structname>CustomPlan</>
+   node being constructed above.
+   The reason why custom-plan provider has to allocate a node object by
+   itself is that we allow to extend the base types to store private
+   fields managed by individual custom-plan providers, thus only custom-
+   plan provider knows actual data size to be allocated.
+  </para>
+  <para>
+   Once a <structname>CustomPlanState</> is constructed, its callback
+   functions are invoked by executor.
+   <structfield>BeginCustomPlan</> shall be invoked on the tail of the
+   custom node initialization, to apply additional initialization
+   according to the private fields.
+   <structfield>ExecCustomPlan</> (or <structfield>MultiExecCustomPlan</>
+   if its parent custom-plan want to exchange data in its own format)
+   shall be invoked during query execution to fetch a row from the
+   custom-plan node. Then, <structfield>EndCustomPlan</> shall be invoked
+   to release all the resources being acquired by this custom-plan
+   node.
+  </para>
+ </sect1>
+
+ <sect1 id="cpp-path-callbacks">
+  <title>Custom Path Callbacks</title>
+  <para>
+   This section introduces callback functions of <structname>CustomPath</>
+   structure; defined in the <structname>CustomPathMethods</>.
+  </para>
+  <para>
+<programlisting>
+Node *
+CreateCustomPlan(PlannerInfo *root,
+                 CustomPath *best_path);
+</programlisting>
+   It populates a <structname>CustomPlan</> (or inherited data type) node
+   according to the supplied <structname>CustomPath</> node which was
+   constructed on the custom plan handler function then chosen by the
+   query planner.
+   Only custom plan provider can know exact size of the node to be
+   allocated, this callback allocate a <structname>CustomPlan</> node
+   with <structname>CustomPlanMethods</> callbacks table 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 custom-plan
+   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>
+ </sect1>
+
+ <sect1 id="cpp-plan-callbacks">
+  <title>Custom Plan Callbacks</title>
+  <para>
+   This section introduces callback functions of <structname>CustomPlan</>
+   structure; defined in the <structname>CustomPlanMethods</>.
+  </para>
+  <para>
+<programlisting>
+Plan      *
+InitCustomPlan(CustomPlan *custom_plan,
+               PlannerInfo *root,
+               CustomPath *best_path,
+               List *tlist,
+               List *clauses);
+</programlisting>
+   It initializes the <structname>CustomPlan</> node being acquired by
+   <structfield>CreateCustomPlan</> callback.
+   The backend takes some common initializations prior to its invocation.
+   <literal>tlist</> and <literal>clauses</> are extracted from the path
+   node according to the usual manner, so all custom plan provider has to
+   do is putting these members if nothing special are done.
+  </para>
+
+  <para>
+<programlisting>
+void
+SetCustomPlanRef(PlannerInfo *root,
+                 CustomPlan *custom_plan,
+                 int rtoffset);
+</programlisting>
+   It adjusts <literal>varno</> and <literal>varattno</> of var-nodes in
+   the expression tree chained from <structname>CustomPlan</> 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
+FinalizeCustomPlan(PlannerInfo *root,
+                   CustomPlan *custom_plan,
+                   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 custom-plan 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 custom-plan 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 *
+CreateCustomPlanState(CustomPlan *custom_plan);
+</programlisting>
+   It populates a <structname>CustomPlanState</> (or inherited data type)
+   node according to the supplied <structname>CustomPlan</> node that was
+   preliminary constructed on the beginning of query executor.
+   Only custom plan provider can know exact size of the node to be
+   allocated, this callback allocate a <structname>CustomPlanState</> node
+   with <structfield>CustomExecMethods</> callbacks table and arbitrary
+   private fields.
+  </para>
+  <para>
+   Note that main purpose of this callback is allocation of
+   <literal>CustomPlanState</> 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
+TextOutCustomPlan(StringInfo str,
+                  const CustomPlan *node);
+</programlisting>
+   It makes a text representation of custom plan node. If custom-plan
+   provider extends <structfield>CustomPlan</> data type, it shall put
+   private fields on the supplied <literal>StringInfo</> with text form.
+   Note that common fields in <structname>CustomPlan</> are handled by
+   backend,
+   so extension needs to do nothing special.
+  </para>
+
+  <para>
+<programlisting>
+CustomPlan *
+CopyCustomPlan(const CustomPlan *from);
+</programlisting>
+   It duplicate a <structname>CustomPlan</> node onto a newly allocated one.
+   If custom-plan provider extends the <structname>CustomPlan</> node,
+   it shall copy the private fields and callback table but no need to
+   copy the common <structname>scan</> field of <structname>CustomPlan</>.
+  </para>
+ </sect1>
+
+ <sect1 id="cpp-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
+BeginCustomPlan(CustomPlanState *node,
+                EState *estate,
+                int eflags);
+</programlisting>
+   It begins execution of custom-plan. This callback is invoked during
+   executor startup to initialize the supplied <structname>CustomPlanState</>
+   that was constructed on the <structfield>CreateCustomPlanState</> above.
+   The custom-plan provider shall have initialization of its private fields
+   and common fields within <structname>CustomPlanState</> if needed, because
+   backend code already initializes expressions on its <literal>targetlist</>
+   and <literal>qual</>, assigns result slot according to the
+   <literal>targetlist</> and also assigns scan slot if <literal>scanrelid</>
+   is valid.
+  </para>
+
+  <para>
+<programlisting>
+TupleTableSlot *
+ExecCustomPlan(CustomPlanState *node);
+</programlisting>
+   It fetches one row from the custom-plan node, returning it in a tuple
+   table slot (<structfield>ps_ResultTupleSlot</> of <structname>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
+   per-scan duration memory.
+  </para>
+
+  <para>
+<programlisting>
+Node *
+MultiExecCustomPlan(CustomPlanState *node);
+</programlisting>
+   It allows to return arbitrary data structure to the upper node, unlike
+   usual <structfield>ExecCustomPlan</>. Built-in code has no invocation
+   path to call <function>MultiExecProcNode</> towards
+   <structfield>CustomPlanState</> node, so it is invoked only when
+   a particular custom-plan provider made a stacked custom-plan nodes and
+   calls <structfield>MultiExecProcNode</> to this underlying node.
+  </para>
+  <para>
+   Note that it is custom-plan provider's responsibility to translate the
+   arbitrary data structure into <productname>PostgreSQL</>'s complianced
+   data structure when top-level <structname>CustomPlanState</> returns
+   a row using <structname>ExecCustomPlan</>.
+  </para>
+
+  <para>
+<programlisting>
+void
+EndCustomPlan(CustomPlanState *node);
+</programlisting>
+   It ends the execution of custom plan and release any resources held by
+   this node. If custom-plan provider acquired resources that is not
+   released automatically at end of executor, it is responsibility of the
+   custom plan provider.
+  </para>
+
+  <para>
+<programlisting>
+void
+ReScanCustomPlan(CustomPlanState *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 *
+MarkPosCustomPlan(CustomPlanState *node);
+</programlisting>
+   It is an optional callback if <literal>CPP_FLAGS_SUPPORT_BACKWARD_</>
+   of <structfield>cpp_flags</> is set. Elsewhere, it shall not be called
+   and <literal>NULL</> is suitable.
+   It saves current scan position on somewhere in private fields of
+   <structname>CustomPlanState</>, to restore the position later.
+  </para>
+
+  <para>
+<programlisting>
+void *
+RestrPosCustomPlan(CustomPlanState *node);
+</programlisting>
+   It is an optional callback if <literal>CPP_FLAGS_SUPPORT_BACKWARD_</>
+   of <structfield>cpp_flags</> is set. Elsewhere, it shall not be called
+   and <literal>NULL</> is suitable.
+   It restores the previous scan position saved by
+   the <structname>MarkPosCustomPlan</> above.
+  </para>
+
+  <para>
+<programlisting>
+void
+ExplainCustomPlanTargetRel(CustomPlanState *node,
+                           ExplainState *es);
+</programlisting>
+   It is an optional callback, to show the target relation to be scanned.
+   In most cases, custom plan provider put text representation of the relation
+   to be scanned according to the manner in <function>ExplainTargetRel</>.
+  </para>
+
+  <para>
+<programlisting>
+void
+ExplainCustomPlan(CustomPlanState *node,
+                  List *ancestors,
+                  ExplainState *es);
+</programlisting>
+   It is an optional callback, to show custom-plan specific explain output.
+  </para>
+
+  <para>
+<programlisting>
+void
+ExplainCustomPreScanNode(CustomPlanState *node,
+                         Bitmapset **rels_used);
+</programlisting>
+   It is an optional callback, to inform the backend which relation is
+   referenced. It shall set <literal>scanrelid</> of the target relation.
+   If <literal>NULL</>, it means this custom-plan provider never
+   references base relations by itself.
+  </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>CustomPlanState</> when <command>EXPLAIN</> needs the
+   text form of the column actually referenced.
+   In case when custom-plan provider adjusted <literal>varno</> of varnodes
+   on the expression tree to use special varnos (<literal>INNER_VAR</>,
+   <literal>OUTER_VAR</> or <literal>INDEX_VAR</>), custom-plan 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.
+  </para>
+ </sect1>
+</chapter>
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 5902f97..a9b9efc 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-provider SYSTEM "custom-plan-provider.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..4702178 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -242,6 +242,7 @@
   &nls;
   &plhandler;
   &fdwhandler;
+  &custom-plan-provider;
   &geqo;
   &indexam;
   &gist;
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 781a736..389ab04 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -722,6 +722,14 @@ ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
 			*rels_used = bms_add_member(*rels_used,
 										((Scan *) plan)->scanrelid);
 			break;
+		case T_CustomPlan:
+			{
+				CustomPlanState *cps = (CustomPlanState *)planstate;
+
+				if (cps->methods->ExplainCustomPreScanNode)
+					cps->methods->ExplainCustomPreScanNode(cps, rels_used);
+			}
+			break;
 		case T_ModifyTable:
 			/* cf ExplainModifyTarget */
 			*rels_used = bms_add_member(*rels_used,
@@ -848,6 +856,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 +945,14 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_ForeignScan:
 			pname = sname = "Foreign Scan";
 			break;
+		case T_CustomPlan:
+			sname = "Custom";
+			custom_name = ((CustomPlan *) plan)->methods->CustomName;
+			if (custom_name)
+				pname = psprintf("Custom (%s)", custom_name);
+			else
+				pname = sname;
+			break;
 		case T_Material:
 			pname = sname = "Materialize";
 			break;
@@ -1037,6 +1054,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", custom_name, es);
 	}
 
 	switch (nodeTag(plan))
@@ -1084,6 +1103,14 @@ ExplainNode(PlanState *planstate, List *ancestors,
 					ExplainPropertyText("Index Name", indexname, es);
 			}
 			break;
+		case T_CustomPlan:
+			{
+				CustomPlanState *cps = (CustomPlanState *)planstate;
+
+				if (cps->methods->ExplainCustomPlanTargetRel)
+					cps->methods->ExplainCustomPlanTargetRel(cps, es);
+			}
+			break;
 		case T_ModifyTable:
 			ExplainModifyTarget((ModifyTable *) plan, es);
 			break;
@@ -1353,6 +1380,18 @@ ExplainNode(PlanState *planstate, List *ancestors,
 										   planstate, es);
 			show_foreignscan_info((ForeignScanState *) planstate, es);
 			break;
+		case T_CustomPlan:
+			{
+				CustomPlanState *cps = (CustomPlanState *) planstate;
+
+				show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
+				if (plan->qual)
+					show_instrumentation_count("Rows Removed by Filter", 1,
+											   planstate, es);
+				if (cps->methods->ExplainCustomPlan)
+					cps->methods->ExplainCustomPlan(cps, 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..b6d1f18 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -21,6 +21,7 @@
 #include "executor/nodeBitmapIndexscan.h"
 #include "executor/nodeBitmapOr.h"
 #include "executor/nodeCtescan.h"
+#include "executor/nodeCustom.h"
 #include "executor/nodeForeignscan.h"
 #include "executor/nodeFunctionscan.h"
 #include "executor/nodeGroup.h"
@@ -197,6 +198,10 @@ ExecReScan(PlanState *node)
 			ExecReScanForeignScan((ForeignScanState *) node);
 			break;
 
+		case T_CustomPlanState:
+			ExecReScanCustomPlan((CustomPlanState *) node);
+			break;
+
 		case T_NestLoopState:
 			ExecReScanNestLoop((NestLoopState *) node);
 			break;
@@ -291,6 +296,10 @@ ExecMarkPos(PlanState *node)
 			ExecValuesMarkPos((ValuesScanState *) node);
 			break;
 
+		case T_CustomPlanState:
+			ExecCustomMarkPos((CustomPlanState *) node);
+			break;
+
 		case T_MaterialState:
 			ExecMaterialMarkPos((MaterialState *) node);
 			break;
@@ -348,6 +357,10 @@ ExecRestrPos(PlanState *node)
 			ExecValuesRestrPos((ValuesScanState *) node);
 			break;
 
+		case T_CustomPlanState:
+			ExecCustomRestrPos((CustomPlanState *) node);
+			break;
+
 		case T_MaterialState:
 			ExecMaterialRestrPos((MaterialState *) node);
 			break;
@@ -379,9 +392,9 @@ ExecRestrPos(PlanState *node)
  * and valuesscan support is actually useless code at present.)
  */
 bool
-ExecSupportsMarkRestore(NodeTag plantype)
+ExecSupportsMarkRestore(Path *pathnode)
 {
-	switch (plantype)
+	switch (pathnode->pathtype)
 	{
 		case T_SeqScan:
 		case T_IndexScan:
@@ -403,6 +416,16 @@ ExecSupportsMarkRestore(NodeTag plantype)
 			 */
 			return false;
 
+		case T_CustomPlan:
+			{
+				CustomPath *cpath = (CustomPath *) pathnode;
+
+				Assert(IsA(cpath, CustomPath));
+				if (cpath->cpp_flags & CPP_FLAGS_SUPPORT_MARK_RESTORE)
+					return true;
+			}
+			break;
+
 		default:
 			break;
 	}
@@ -412,7 +435,7 @@ ExecSupportsMarkRestore(NodeTag plantype)
 
 /*
  * ExecSupportsBackwardScan - does a plan type support backwards scanning?
- *
+xs *
  * Ideally, all plan types would support backwards scan, but that seems
  * unlikely to happen soon.  In some cases, a plan node passes the backwards
  * scan down to its children, and so supports backwards scan only if its
@@ -465,6 +488,16 @@ ExecSupportsBackwardScan(Plan *node)
 			return ExecSupportsBackwardScan(((SubqueryScan *) node)->subplan) &&
 				TargetListSupportsBackwardScan(node->targetlist);
 
+		case T_CustomPlan:
+			{
+				uint32	cpp_flags = ((CustomPlan *) node)->cpp_flags;
+
+				if (TargetListSupportsBackwardScan(node->targetlist) &&
+					(cpp_flags & CPP_FLAGS_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..ae77283 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_CustomPlan:
+			result = (PlanState *) ExecInitCustomPlan((CustomPlan *) node,
+													  estate, eflags);
+			break;
+
 			/*
 			 * join nodes
 			 */
@@ -442,6 +448,10 @@ ExecProcNode(PlanState *node)
 			result = ExecForeignScan((ForeignScanState *) node);
 			break;
 
+		case T_CustomPlanState:
+			result = ExecCustomPlan((CustomPlanState *) node);
+			break;
+
 			/*
 			 * join nodes
 			 */
@@ -558,6 +568,10 @@ MultiExecProcNode(PlanState *node)
 			result = MultiExecBitmapOr((BitmapOrState *) node);
 			break;
 
+		case T_CustomPlanState:
+			result = MultiExecCustomPlan((CustomPlanState *) node);
+			break;
+
 		default:
 			elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
 			result = NULL;
@@ -678,6 +692,10 @@ ExecEndNode(PlanState *node)
 			ExecEndForeignScan((ForeignScanState *) node);
 			break;
 
+		case T_CustomPlanState:
+			ExecEndCustomPlan((CustomPlanState *) node);
+			break;
+
 			/*
 			 * join nodes
 			 */
diff --git a/src/backend/executor/nodeCustom.c b/src/backend/executor/nodeCustom.c
new file mode 100644
index 0000000..785909e
--- /dev/null
+++ b/src/backend/executor/nodeCustom.c
@@ -0,0 +1,147 @@
+/* ------------------------------------------------------------------------
+ *
+ * nodeCustom.c
+ *		Routines to handle execution of custom plan 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"
+
+CustomPlanState *
+ExecInitCustomPlan(CustomPlan *cplan, EState *estate, int eflags)
+{
+	CustomPlanState    *cps;
+
+	/* populate a CustomPlanState according to the CustomPlan */
+	cps = (CustomPlanState *)cplan->methods->CreateCustomPlanState(cplan);
+	Assert(IsA(cps, CustomPlanState));
+
+	/* fill up fields of PlanState */
+	cps->ss.ps.plan = &cplan->scan.plan;
+	cps->ss.ps.state = estate;
+
+	/* create expression context for node */
+	ExecAssignExprContext(estate, &cps->ss.ps);
+	cps->ss.ps.ps_TupFromTlist = false;
+
+	/* initialize child expressions */
+	cps->ss.ps.targetlist = (List *)
+		ExecInitExpr((Expr *) cplan->scan.plan.targetlist,
+					 (PlanState *) cps);
+	cps->ss.ps.qual = (List *)
+		ExecInitExpr((Expr *) cplan->scan.plan.qual,
+					 (PlanState *) cps);
+
+	/* initialization of result tuple slot  */
+	ExecInitResultTupleSlot(estate, &cps->ss.ps);
+	ExecAssignResultTypeFromTL(&cps->ss.ps);
+
+	if (cplan->scan.scanrelid > 0)
+	{
+		Relation	heap_rel;
+
+		heap_rel = ExecOpenScanRelation(estate, cplan->scan.scanrelid, eflags);
+		cps->ss.ss_currentRelation = heap_rel;
+		cps->ss.ss_currentScanDesc = NULL;	/* set by provider on demand */
+		ExecInitScanTupleSlot(estate, &cps->ss);
+		ExecAssignScanType(&cps->ss, RelationGetDescr(heap_rel));
+		ExecAssignScanProjectionInfo(&cps->ss);
+	}
+	else
+	{
+		/*
+		 * Elsewhere, custom-plan provider should be responsible to put
+		 * appropriate initialization of scan tuple-slot and projection
+		 * info.
+		 */
+		cps->ss.ss_currentRelation = NULL;
+		cps->ss.ss_currentScanDesc = NULL;
+		cps->ss.ss_ScanTupleSlot = NULL;
+		cps->ss.ps.ps_ProjInfo = NULL;
+	}
+	/*
+	 * Then, custom-plan provider can have all the own original
+	 * initialization on demand.
+	 */
+	cps->methods->BeginCustomPlan(cps, estate, eflags);
+
+	return cps;
+}
+
+TupleTableSlot *
+ExecCustomPlan(CustomPlanState *cpstate)
+{
+	Assert(cpstate->methods->ExecCustomPlan != NULL);
+	return cpstate->methods->ExecCustomPlan(cpstate);
+}
+
+Node *
+MultiExecCustomPlan(CustomPlanState *cpstate)
+{
+	if (!cpstate->methods->MultiExecCustomPlan)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("CustomPlan \"%s\" does not support MultiExec method",
+						cpstate->methods->CustomName)));
+	return cpstate->methods->MultiExecCustomPlan(cpstate);
+}
+
+void
+ExecEndCustomPlan(CustomPlanState *cpstate)
+{
+	Assert(cpstate->methods->EndCustomPlan != NULL);
+	cpstate->methods->EndCustomPlan(cpstate);
+
+	/* Free the exprcontext */
+	ExecFreeExprContext(&cpstate->ss.ps);
+
+	/* Clean out the tuple table */
+	ExecClearTuple(cpstate->ss.ps.ps_ResultTupleSlot);
+	if (cpstate->ss.ss_ScanTupleSlot)
+		ExecClearTuple(cpstate->ss.ss_ScanTupleSlot);
+
+	/* Close the heap relation, if needed */
+	if (cpstate->ss.ss_currentRelation)
+		ExecCloseScanRelation(cpstate->ss.ss_currentRelation);
+}
+
+void
+ExecReScanCustomPlan(CustomPlanState *cpstate)
+{
+	Assert(cpstate->methods->ReScanCustomPlan != NULL);
+	cpstate->methods->ReScanCustomPlan(cpstate);
+}
+
+void
+ExecCustomMarkPos(CustomPlanState *cpstate)
+{
+	if (!cpstate->methods->MarkPosCustomPlan)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+		errmsg("MarkPos is not supported by custom plan provider: %s",
+			   cpstate->methods->CustomName)));
+	cpstate->methods->MarkPosCustomPlan(cpstate);
+}
+
+void
+ExecCustomRestrPos(CustomPlanState *cpstate)
+{
+	if (!cpstate->methods->RestrPosCustomPlan)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+		errmsg("RestrPos is not supported by custom plan provider: %s",
+			   cpstate->methods->CustomName)));
+	cpstate->methods->RestrPosCustomPlan(cpstate);
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 3088578..bc05246 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -598,6 +598,21 @@ _copyForeignScan(const ForeignScan *from)
 }
 
 /*
+ * _copyCustomPlan
+ */
+static CustomPlan *
+_copyCustomPlan(const CustomPlan *from)
+{
+	CustomPlan *newnode;
+
+	newnode = from->methods->CopyCustomPlan(from);
+	Assert(nodeTag(newnode) == nodeTag(from));
+	CopyScanFields((const Scan *) from, (Scan *) newnode);
+
+	return newnode;
+}
+
+/*
  * CopyJoinFields
  *
  *		This function copies the fields of the Join node.  It is used by
@@ -4012,6 +4027,9 @@ copyObject(const void *from)
 		case T_ForeignScan:
 			retval = _copyForeignScan(from);
 			break;
+		case T_CustomPlan:
+			retval = _copyCustomPlan(from);
+			break;
 		case T_Join:
 			retval = _copyJoin(from);
 			break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e686a6c..511f443 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -564,6 +564,17 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
 }
 
 static void
+_outCustomPlan(StringInfo str, const CustomPlan *node)
+{
+	WRITE_NODE_TYPE("CUSTOMPLAN");
+
+	_outScanInfo(str, (const Scan *) node);
+	appendStringInfo(str, " :methods");
+	_outToken(str, node->methods->CustomName);
+	node->methods->TextOutCustomPlan(str, node);
+}
+
+static void
 _outJoin(StringInfo str, const Join *node)
 {
 	WRITE_NODE_TYPE("JOIN");
@@ -1582,6 +1593,16 @@ _outForeignPath(StringInfo str, const ForeignPath *node)
 }
 
 static void
+_outCustomPath(StringInfo str, const CustomPath *node)
+{
+	WRITE_NODE_TYPE("CUSTOMPATH");
+	_outPathInfo(str, (const Path *) node);
+	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");
@@ -2851,6 +2872,9 @@ _outNode(StringInfo str, const void *obj)
 			case T_ForeignScan:
 				_outForeignScan(str, obj);
 				break;
+			case T_CustomPlan:
+				_outCustomPlan(str, obj);
+				break;
 			case T_Join:
 				_outJoin(str, obj);
 				break;
@@ -3059,6 +3083,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..dbba5ae 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -336,7 +336,7 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 				}
 				break;
 			case RTE_SUBQUERY:
-				/* Subquery --- fully handled during set_rel_size */
+				/* Subquery --- path was added during set_rel_size */
 				break;
 			case RTE_FUNCTION:
 				/* RangeFunction */
@@ -347,12 +347,17 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 				set_values_pathlist(root, rel, rte);
 				break;
 			case RTE_CTE:
-				/* CTE reference --- fully handled during set_rel_size */
+				/* CTE reference --- path was added during set_rel_size */
 				break;
 			default:
 				elog(ERROR, "unexpected rtekind: %d", (int) rel->rtekind);
 				break;
 		}
+		/* Also, consider paths by custom-plan providers */
+		call_custom_scan_providers(root, rel, rte);
+
+		/* Select cheapest path */
+		set_cheapest(rel);
 	}
 
 #ifdef OPTIMIZER_DEBUG
@@ -401,9 +406,6 @@ set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 
 	/* Consider TID scans */
 	create_tidscan_paths(root, rel);
-
-	/* Now find the cheapest of the paths for this rel */
-	set_cheapest(rel);
 }
 
 /*
@@ -429,9 +431,6 @@ set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 {
 	/* Call the FDW's GetForeignPaths function to generate path(s) */
 	rel->fdwroutine->GetForeignPaths(root, rel, rte->relid);
-
-	/* Select cheapest path */
-	set_cheapest(rel);
 }
 
 /*
@@ -1272,9 +1271,6 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
 
 	/* Generate appropriate path */
 	add_path(rel, create_subqueryscan_path(root, rel, pathkeys, required_outer));
-
-	/* Select cheapest path (pretty easy in this case...) */
-	set_cheapest(rel);
 }
 
 /*
@@ -1343,9 +1339,6 @@ set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 	/* Generate appropriate path */
 	add_path(rel, create_functionscan_path(root, rel,
 										   pathkeys, required_outer));
-
-	/* Select cheapest path (pretty easy in this case...) */
-	set_cheapest(rel);
 }
 
 /*
@@ -1366,9 +1359,6 @@ set_values_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 
 	/* Generate appropriate path */
 	add_path(rel, create_valuesscan_path(root, rel, required_outer));
-
-	/* Select cheapest path (pretty easy in this case...) */
-	set_cheapest(rel);
 }
 
 /*
@@ -1435,9 +1425,6 @@ set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 
 	/* Generate appropriate path */
 	add_path(rel, create_ctescan_path(root, rel, required_outer));
-
-	/* Select cheapest path (pretty easy in this case...) */
-	set_cheapest(rel);
 }
 
 /*
@@ -1488,9 +1475,6 @@ set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 
 	/* Generate appropriate path */
 	add_path(rel, create_worktablescan_path(root, rel, required_outer));
-
-	/* Select cheapest path (pretty easy in this case...) */
-	set_cheapest(rel);
 }
 
 /*
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 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 4b641a2..f7a04f1 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -77,13 +77,13 @@ 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 Plan *create_custom_plan(PlannerInfo *root, CustomPath *best_path);
 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,
 					  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);
@@ -261,6 +261,9 @@ create_plan_recurse(PlannerInfo *root, Path *best_path)
 			plan = create_unique_plan(root,
 									  (UniquePath *) best_path);
 			break;
+		case T_CustomPlan:
+			plan = create_custom_plan(root, (CustomPath *) best_path);
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) best_path->pathtype);
@@ -1072,6 +1075,96 @@ 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 'scan_clauses' and targetlist 'tlist'.
+ */
+static Plan *
+create_custom_plan(PlannerInfo *root, CustomPath *best_path)
+{
+	CustomPlan	   *custom_plan;
+	RelOptInfo	   *rel;
+	List		   *tlist = NIL;
+	List		   *clauses = NIL;
+
+	/*
+	 * Create a custom-plan object delivered from CustomPlan type,
+	 * according to the supplied CustomPath
+	 */
+	Assert(best_path->path.pathtype == T_CustomPlan);
+	custom_plan = (CustomPlan *)
+		best_path->methods->CreateCustomPlan(root, best_path);
+	Assert(nodeTag(custom_plan) == best_path->path.pathtype);
+
+	rel = best_path->path.parent;
+	if (rel)
+	{
+		if (rel->reloptkind == RELOPT_BASEREL)
+		{
+			Assert(rel->relid > 0);
+			custom_plan->scan.scanrelid = rel->relid;
+
+			/*
+			 * For table scans, rather than using the relation targetlist
+			 * (which is only those Vars actually needed by the query),
+			 * we prefer to generate a tlist containing all Vars in order.
+			 * This will allow the executor to optimize away projection of
+			 * the table tuples, if possible.
+			 */
+			if (use_physical_tlist(root, rel))
+				tlist = build_physical_tlist(root, rel);
+		}
+		/* elsewhere, we generate a tlist from the relation targetlist */
+		if (tlist == NIL)
+			tlist = build_path_tlist(root, &best_path->path);
+
+		/*
+		 * Extract the relevant restriction clauses from the parent relation.
+		 * The executor must apply all these restrictions during the scan,
+		 * except for pseudoconstants which we'll take care of below.
+		 */
+		clauses = rel->baserestrictinfo;
+
+		/*
+		 * If this is a parameterized scan, we also need to enforce all
+		 * the join clauses available from the outer relation(s).
+		 */
+		if (best_path->path.param_info)
+			clauses = list_concat(list_copy(clauses),
+								  best_path->path.param_info->ppi_clauses);
+
+		/* Sort clauses into best execution order */
+		clauses = order_qual_clauses(root, clauses);
+
+		/*
+		 * Replace outer-relation variables with nestloop params.
+		 * Note that any other clauses which is managed by extension
+		 * itself has to be handled in InitCustomPlan() method, as
+		 * built-in code doing.
+		 */
+		if (best_path->path.param_info)
+			clauses = (List *)replace_nestloop_params(root, (Node *)clauses);
+	}
+	/*
+	 * Copy cost data from Path to Plan; no need to make custom-plan
+	 * providers do this
+	 */
+	copy_path_costsize((Plan *)custom_plan, (Path *)best_path);
+
+	/*
+	 * Let the custom-plan provider perform its final initialization
+	 * of this CustomPlan (to be an inherited type, actually) node
+	 * according to its own necessity.
+	 * Note that custom-plan provider may/can replace (or stack another
+	 * one on) its own custom-plan node on demand, for example, to add
+	 * Result node to handle pseudo constant using create_gating_plan().
+	 */
+	return custom_plan->methods->InitCustomPlan(custom_plan,
+												root, best_path,
+												tlist, clauses);
+}
 
 /*****************************************************************************
  *
@@ -2540,7 +2633,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 4d717df..c416859 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,14 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 			}
 			break;
 
+		case T_CustomPlan:
+			{
+				CustomPlan *cplan = (CustomPlan *) plan;
+
+				cplan->methods->SetCustomPlanRef(root, cplan, rtoffset);
+			}
+			break;
+
 		case T_NestLoop:
 		case T_MergeJoin:
 		case T_HashJoin:
@@ -1158,7 +1165,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..9833073 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -2283,6 +2283,30 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
 			context.paramids = bms_add_members(context.paramids, scan_params);
 			break;
 
+		case T_CustomPlan:
+			{
+				CustomPlan *cplan = (CustomPlan *) plan;
+
+				/*
+				 * If this custom-plan scab a particular relation, we
+				 * adjust paramids as other scan derivered node.
+				 */
+				if (cplan->scan.scanrelid > 0)
+					context.paramids = bms_add_members(context.paramids,
+													   scan_params);
+				/*
+				 * custom plan provider is responsible to apply
+				 * finalize_primnode() on the expression node of its
+				 * private fields, but no need to apply tlist and
+				 * qual of Plan node (already done above).
+				 */
+				if (cplan->methods->FinalizeCustomPlan)
+					cplan->methods->FinalizeCustomPlan(root, cplan,
+													   finalize_primnode,
+													   (void *)&context);
+			}
+			break;
+
 		case T_ModifyTable:
 			{
 				ModifyTable *mtplan = (ModifyTable *) plan;
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 319e8b2..43b56b1 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -16,6 +16,9 @@
 
 #include <math.h>
 
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
@@ -26,8 +29,11 @@
 #include "optimizer/restrictinfo.h"
 #include "optimizer/var.h"
 #include "parser/parsetree.h"
+#include "utils/inval.h"
 #include "utils/lsyscache.h"
+#include "utils/memutils.h"
 #include "utils/selfuncs.h"
+#include "utils/syscache.h"
 
 
 typedef enum
@@ -1926,3 +1932,76 @@ reparameterize_path(PlannerInfo *root, Path *path,
 	}
 	return NULL;
 }
+
+/*****************************************************************************
+ *		creation of custom-plan paths
+ *****************************************************************************/
+typedef struct
+{
+	const char	   *name;
+	union {
+		add_custom_scan_path_type	scan_callback;
+	};
+} custom_plan_provider_info;
+
+static List *custom_scan_providers = NIL;
+static MemoryContext custom_plan_memcxt = NULL;
+
+/*
+ * register_custom_scan_provider
+ *
+ * It registers an entrypoing of custom-plan provider for scanning.
+ * The callback function is expected to construct CustomPath node
+ * to scan a particular relation to be scanned, if its logic can
+ * provide alternative way to scan.
+ */
+void
+register_custom_scan_provider(const char *cpp_name,
+							  add_custom_scan_path_type callback)
+{
+	custom_plan_provider_info  *cpp_info;
+	MemoryContext	oldcxt;
+
+	if (!custom_plan_memcxt)
+	{
+		custom_plan_memcxt = AllocSetContextCreate(TopMemoryContext,
+												   "custom-plan providers",
+												   ALLOCSET_DEFAULT_MINSIZE,
+												   ALLOCSET_DEFAULT_INITSIZE,
+												   ALLOCSET_DEFAULT_MAXSIZE);
+	}
+	oldcxt = MemoryContextSwitchTo(custom_plan_memcxt);
+	cpp_info = palloc(sizeof(custom_plan_provider_info));
+	cpp_info->name = pstrdup(cpp_name);
+	cpp_info->scan_callback = callback;
+
+	custom_scan_providers = lappend(custom_scan_providers, cpp_info);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * call_custom_scan_providers
+ *
+ * A callchain on relation scan. custom-plan provider can add alternative
+ * scan paths derived from CustomPath class.
+ */
+void
+call_custom_scan_providers(PlannerInfo *root,
+						   RelOptInfo *baserel,
+						   RangeTblEntry *rte)
+{
+	customScanArg	sarg;
+	ListCell	   *cell;
+
+	sarg.root = root;
+	sarg.baserel = baserel;
+	sarg.rte = rte;
+
+	foreach (cell, custom_scan_providers)
+	{
+		custom_plan_provider_info  *cpp_info = lfirst(cell);
+
+		cpp_info->scan_callback(&sarg);
+	}
+}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 7237e5d..3a949d6 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -5490,6 +5490,29 @@ 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-plan provider replaced a varno in expression tree by special
+ * varno.
+ */
+static Node *
+GetSpecialCustomVar(PlanState *ps, Var *varnode, PlanState **child_ps)
+{
+	CustomPlanState *cps = (CustomPlanState *) ps;
+
+	Assert(IsA(ps, CustomPlanState));
+	Assert(IS_SPECIAL_VARNO(varnode->varno));
+
+	if (!cps->methods->GetSpecialCustomVar)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("%s does not support special varno reference",
+						cps->methods->CustomName)));
+	return (Node *)cps->methods->GetSpecialCustomVar(cps, varnode, child_ps);
+}
 
 /*
  * Display a Var appropriately.
@@ -5519,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;
 
@@ -5543,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, CustomPlanState) &&
+			 (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 +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;
 
@@ -5831,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, CustomPlanState) &&
+			 (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/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index f8b4a65..fc798a2 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");
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 239aff3..6c35089 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..abe1e94
--- /dev/null
+++ b/src/include/executor/nodeCustom.h
@@ -0,0 +1,30 @@
+/* ------------------------------------------------------------------------
+ *
+ * nodeCustom.h
+ *
+ * prototypes for CustomPlan 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 CustomPlanState *ExecInitCustomPlan(CustomPlan *cplan,
+										   EState *estate, int eflags);
+extern TupleTableSlot *ExecCustomPlan(CustomPlanState *node);
+extern Node *MultiExecCustomPlan(CustomPlanState *node);
+extern void ExecEndCustomPlan(CustomPlanState *node);
+
+extern void ExecReScanCustomPlan(CustomPlanState *node);
+extern void ExecCustomMarkPos(CustomPlanState *node);
+extern void ExecCustomRestrPos(CustomPlanState *node);
+
+#endif	/* NODECUSTOM_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index b271f21..5d6dee5 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,49 @@ typedef struct ForeignScanState
 	void	   *fdw_state;		/* foreign-data wrapper can keep state here */
 } ForeignScanState;
 
+/* ----------------
+ * CustomPlanState information
+ *
+ *		CustomPlan nodes are used to execute custom code within executor.
+ * ----------------
+ */
+struct CustomExecMethods;
+struct ExplainState;	/* to avoid to include explain.h here */
+
+typedef struct CustomPlanState
+{
+	ScanState	ss;
+	uint32		cpp_flags;	/* mask of CPP_FLAGS_* defined in relation.h */
+	const struct CustomExecMethods *methods;
+} CustomPlanState;
+
+typedef struct CustomExecMethods
+{
+	const char     *CustomName;
+
+	/* EXECUTOR methods */
+	void    (*BeginCustomPlan)(CustomPlanState *node,
+							   EState *estate,
+							   int eflags);
+	TupleTableSlot *(*ExecCustomPlan)(CustomPlanState *node);
+	Node   *(*MultiExecCustomPlan)(CustomPlanState *node);
+	void	(*EndCustomPlan)(CustomPlanState *node);
+	void	(*ReScanCustomPlan)(CustomPlanState *node);
+	void	(*MarkPosCustomPlan)(CustomPlanState *node);
+	void	(*RestrPosCustomPlan)(CustomPlanState *node);
+
+	/* EXPLAIN support */
+	void	(*ExplainCustomPlanTargetRel)(CustomPlanState *node,
+										  struct ExplainState *es);
+	void    (*ExplainCustomPlan)(CustomPlanState *node,
+								 List *ancestors,
+								 struct ExplainState *es);
+	void	(*ExplainCustomPreScanNode)(CustomPlanState *node,
+										Bitmapset **rels_used);
+	Node   *(*GetSpecialCustomVar)(CustomPlanState *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 067c768..95d4fed 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_CustomPlan,
 	T_Join,
 	T_NestLoop,
 	T_MergeJoin,
@@ -107,6 +108,7 @@ typedef enum NodeTag
 	T_CteScanState,
 	T_WorkTableScanState,
 	T_ForeignScanState,
+	T_CustomPlanState,
 	T_JoinState,
 	T_NestLoopState,
 	T_MergeJoinState,
@@ -224,6 +226,7 @@ typedef enum NodeTag
 	T_HashPath,
 	T_TidPath,
 	T_ForeignPath,
+	T_CustomPath,
 	T_AppendPath,
 	T_MergeAppendPath,
 	T_ResultPath,
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 3b9c683..0886eb0 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"
 
 
 /* ----------------------------------------------------------------
@@ -479,6 +481,40 @@ typedef struct ForeignScan
 	bool		fsSystemCol;	/* true if any "system column" is needed */
 } ForeignScan;
 
+/* ----------------
+ *     CustomPlan node
+ * ----------------
+ */
+struct CustomPlanMethods;
+struct PlannerInfo;			/* to avoid to include nodes/relation.h here */
+
+typedef struct CustomPlan
+{
+	Scan		scan;
+	uint32		cpp_flags;	/* mask of CPP_FLAGS_* defined in relation.h*/
+	const struct CustomPlanMethods *methods;
+} CustomPlan;
+
+typedef struct CustomPlanMethods
+{
+	const char *CustomName;
+	Plan	  *(*InitCustomPlan)(CustomPlan *custom_plan,
+								 struct PlannerInfo *root,
+								 struct CustomPath *best_path,
+								 List *tlist,
+								 List *clauses);
+	void	   (*SetCustomPlanRef)(struct PlannerInfo *root,
+								   CustomPlan *custom_plan,
+								   int rtoffset);
+	void	   (*FinalizeCustomPlan)(struct PlannerInfo *root,
+									 CustomPlan *custom_plan,
+									 bool (*finalize_primnode)(),
+									 void *finalize_context);
+	Node	  *(*CreateCustomPlanState)(CustomPlan *custom_plan);
+	void	   (*TextOutCustomPlan)(StringInfo str,
+									const CustomPlan *node);
+	CustomPlan *(*CopyCustomPlan)(const CustomPlan *from);
+} CustomPlanMethods;
 
 /*
  * ==========
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index dacbe9c..c997ffb 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"
@@ -881,6 +882,35 @@ 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;
+
+#define CPP_FLAGS_SUPPORT_BACKWARD_SCAN		0x0001
+#define CPP_FLAGS_SUPPORT_MARK_RESTORE		0x0002
+
+typedef struct CustomPath
+{
+	Path        path;
+	uint32		cpp_flags;
+	const struct CustomPathMethods *methods;
+} CustomPath;
+
+typedef struct CustomPathMethods
+{
+	const char *CustomName;
+	Node   *(*CreateCustomPlan)(PlannerInfo *root,
+								CustomPath *best_path);
+	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..36da9a3 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -129,6 +129,24 @@ extern Path *reparameterize_path(PlannerInfo *root, Path *path,
 					double loop_count);
 
 /*
+ * Interface of custom-plan provider's entrypoint, for scanning.
+ */
+typedef struct {
+	PlannerInfo	   *root;
+	RelOptInfo	   *baserel;
+	RangeTblEntry  *rte;
+} customScanArg;
+
+typedef void (*add_custom_scan_path_type)(customScanArg *arg);
+
+extern void register_custom_scan_provider(const char *cpp_name,
+										  add_custom_scan_path_type callback);
+
+extern void call_custom_scan_providers(PlannerInfo *root,
+									   RelOptInfo *baserel,
+									   RangeTblEntry *rte);
+
+/*
  * prototypes for relnode.c
  */
 extern void setup_simple_rel_arrays(PlannerInfo *root);
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 4504250..fbf1a0b 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,7 @@ 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 set_opfuncid(OpExpr *opexpr);
 extern void set_sa_opfuncid(ScalarArrayOpExpr *opexpr);
 extern void record_plan_function_dependency(PlannerInfo *root, Oid funcid);
#24Robert Haas
robertmhaas@gmail.com
In reply to: Kouhei Kaigai (#23)

On Wed, Aug 27, 2014 at 6:51 PM, Kouhei Kaigai <kaigai@ak.jp.nec.com> wrote:

I'd like to follow this direction, and start stripping the DDL support.

...please make it so.

The attached patch eliminates DDL support.

Instead of the new CREATE CUSTOM PLAN PROVIDER statement,
it adds an internal function; register_custom_scan_provider
that takes custom plan provider name and callback function
to add alternative scan path (should have a form of CustomPath)
during the query planner is finding out the cheapest path to
scan the target relation.
Also, documentation stuff is revised according to the latest
design.
Any other stuff keeps the previous design.

Comments:

1. There seems to be no reason for custom plan nodes to have MultiExec
support; I think this as an area where extensibility is extremely
unlikely to work out. The MultiExec mechanism is really only viable
between closely-cooperating nodes, like Hash and HashJoin, or
BitmapIndexScan, BitmapAnd, BitmapOr, and BitmapHeapScan; and arguably
those things could have been written as a single, more complex node.
Are we really going to want to support a custom plan that can
substitute for a Hash or BitmapAnd node? I really doubt that's very
useful.

2. This patch is still sort of on the fence about whether we're
implementing custom plans (of any type) or custom scans (thus, of some
particular relation). I previously recommended that we confine
ourselves initially to the task of adding custom *scans* and leave the
question of other kinds of custom plan nodes to a future patch. After
studying the latest patch, I'm inclined to suggest a slightly revised
strategy. This patch is really adding THREE kinds of custom objects:
CustomPlanState, CustomPlan, and CustomPath. CustomPlanState inherits
from ScanState, so it is not really a generic CustomPlan, but
specifically a CustomScan; likewise, CustomPlan inherits from Scan,
and is therefore a CustomScan, not a CustomPlan. But CustomPath is
different: it's just a Path. Even if we only have the hooks to inject
CustomPaths that are effectively scans at this point, I think that
part of the infrastructure could be somewhat generic. Perhaps
eventually we have CustomPath which can generate either CustomScan or
CustomJoin which in turn could generate CustomScanState and
CustomJoinState.

For now, I propose that we rename CustomPlan and CustomPlanState to
CustomScan and CustomScanState, because that's what they are; but that
we leave CustomPath as-is. For ease of review, I also suggest
splitting this into a series of three patches: (1) add support for
CustomPath; (2) add support for CustomScan and CustomScanState; (3)
ctidscan.

3. Is it really a good idea to invoke custom scan providers for RTEs
of every type? It's pretty hard to imagine that a custom scan
provider can do anything useful with, say, RTE_VALUES. Maybe an
accelerated scan of RTE_CTE or RTE_SUBQUERY is practical somehow, but
even that feels like an awfully big stretch. At least until clear use
cases emerge, I'd be inclined to restrict this to RTE_RELATION scans
where rte->relkind != RELKIND_FOREIGN_TABLE; that is, put the logic in
set_plain_rel_pathlist() rather than set_rel_pathlist().

(We might even want to consider whether the hook in
set_plain_rel_pathlist() ought to be allowed to inject a non-custom
plan; e.g. substitute a scan of relation B for a scan of relation A.
For example, imagine that B contains all rows from A that satisfy some
predicate. This could even be useful for foreign tables; e.g.
substitute a scan of a local copy of a foreign table for a reference
to that table. But I put all of these ideas in parentheses because
they're only good ideas to the extent that they don't sidetrack us too
much.)

4. Department of minor nitpicks. You've got a random 'xs' in the
comments for ExecSupportsBackwardScan. And, in contrib/ctidscan,
ctidscan_path_methods, ctidscan_plan_methods, and
ctidscan_exec_methods can have static initializers; there's no need to
initialize them at run time in _PG_init().

--
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

#25Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Robert Haas (#24)

2014-08-29 13:33 GMT-04:00 Robert Haas <robertmhaas@gmail.com>:

On Wed, Aug 27, 2014 at 6:51 PM, Kouhei Kaigai <kaigai@ak.jp.nec.com> wrote:

I'd like to follow this direction, and start stripping the DDL support.

...please make it so.

The attached patch eliminates DDL support.

Instead of the new CREATE CUSTOM PLAN PROVIDER statement,
it adds an internal function; register_custom_scan_provider
that takes custom plan provider name and callback function
to add alternative scan path (should have a form of CustomPath)
during the query planner is finding out the cheapest path to
scan the target relation.
Also, documentation stuff is revised according to the latest
design.
Any other stuff keeps the previous design.

Comments:

1. There seems to be no reason for custom plan nodes to have MultiExec
support; I think this as an area where extensibility is extremely
unlikely to work out. The MultiExec mechanism is really only viable
between closely-cooperating nodes, like Hash and HashJoin, or
BitmapIndexScan, BitmapAnd, BitmapOr, and BitmapHeapScan; and arguably
those things could have been written as a single, more complex node.
Are we really going to want to support a custom plan that can
substitute for a Hash or BitmapAnd node? I really doubt that's very
useful.

This intends to allows a particular custom-scan provider to exchange
its internal data when multiple custom-scan node is stacked.
So, it can be considered a facility to implement closely-cooperating nodes;
both of them are managed by same custom-scan provider.
An example is gpu-accelerated version of hash-join that takes underlying
custom-scan node that will returns a hash table with gpu preferable data
structure, but should not be a part of row-by-row interface.
I believe it is valuable for some use cases, even though I couldn't find
a use-case in ctidscan example.

2. This patch is still sort of on the fence about whether we're
implementing custom plans (of any type) or custom scans (thus, of some
particular relation). I previously recommended that we confine
ourselves initially to the task of adding custom *scans* and leave the
question of other kinds of custom plan nodes to a future patch. After
studying the latest patch, I'm inclined to suggest a slightly revised
strategy. This patch is really adding THREE kinds of custom objects:
CustomPlanState, CustomPlan, and CustomPath. CustomPlanState inherits
from ScanState, so it is not really a generic CustomPlan, but
specifically a CustomScan; likewise, CustomPlan inherits from Scan,
and is therefore a CustomScan, not a CustomPlan. But CustomPath is
different: it's just a Path. Even if we only have the hooks to inject
CustomPaths that are effectively scans at this point, I think that
part of the infrastructure could be somewhat generic. Perhaps
eventually we have CustomPath which can generate either CustomScan or
CustomJoin which in turn could generate CustomScanState and
CustomJoinState.

Suggestion seems to me reasonable. The reason why CustomPlanState
inheris ScanState and CustomPlan inherits Scan is, just convenience for
implementation of extensions. Some useful internal APIs, like ExecScan(),
takes argument of ScanState, so it was a better strategy to choose
Scan/ScanState instead of the bare Plan/PlanState.
Anyway, I'd like to follow the perspective that looks CustomScan as one
derivative from the CustomPath. It is more flexible.

For now, I propose that we rename CustomPlan and CustomPlanState to
CustomScan and CustomScanState, because that's what they are; but that
we leave CustomPath as-is. For ease of review, I also suggest
splitting this into a series of three patches: (1) add support for
CustomPath; (2) add support for CustomScan and CustomScanState; (3)
ctidscan.

OK, I'll do that.

3. Is it really a good idea to invoke custom scan providers for RTEs
of every type? It's pretty hard to imagine that a custom scan
provider can do anything useful with, say, RTE_VALUES. Maybe an
accelerated scan of RTE_CTE or RTE_SUBQUERY is practical somehow, but
even that feels like an awfully big stretch. At least until clear use
cases emerge, I'd be inclined to restrict this to RTE_RELATION scans
where rte->relkind != RELKIND_FOREIGN_TABLE; that is, put the logic in
set_plain_rel_pathlist() rather than set_rel_pathlist().

I'd like to agree. Indeed, it's not easy to assume a use case of
custom-logic for non-plain relations.

(We might even want to consider whether the hook in
set_plain_rel_pathlist() ought to be allowed to inject a non-custom
plan; e.g. substitute a scan of relation B for a scan of relation A.
For example, imagine that B contains all rows from A that satisfy some
predicate. This could even be useful for foreign tables; e.g.
substitute a scan of a local copy of a foreign table for a reference
to that table. But I put all of these ideas in parentheses because
they're only good ideas to the extent that they don't sidetrack us too
much.)

Hmm... It seems to me we need another infrastructure to take
a substitute scan, because add_path() is called towards a certain
RelOpInfo that is associated with the relation A.
As long as custom-scan provider "internally" redirect a request for
scan of A by substitute scan B (with taking care of all other stuff
like relation locks), I don't think we need to put some other hooks
outside from the set_plain_rel_pathlist().

4. Department of minor nitpicks. You've got a random 'xs' in the
comments for ExecSupportsBackwardScan.

Sorry, I didn't type 'ctrl' well when I saved the source code on emacs...

And, in contrib/ctidscan,
ctidscan_path_methods, ctidscan_plan_methods, and
ctidscan_exec_methods can have static initializers; there's no need to
initialize them at run time in _PG_init().

It came from the discussion I had long time before during patch
reviewing of postgres_fdw. I suggested to use static table of
FdwRoutine but I got a point that says some compiler raise
error/warning to put function pointers on static initialization.
I usually use GCC only, so I'm not sure whether this argue is
right or not, even though the postgres_fdw_handler() allocates
FdwRoutine using palloc() then put function pointers for each.

Anyway, I'll start to revise the patch according to the comments
2, 3 and first half of 4. Also, I'd like to see the comments regarding
to the 1 and later half of 4.

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

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

On Sun, Aug 31, 2014 at 12:54 AM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

2014-08-29 13:33 GMT-04:00 Robert Haas <robertmhaas@gmail.com>:

On Wed, Aug 27, 2014 at 6:51 PM, Kouhei Kaigai <kaigai@ak.jp.nec.com> wrote:

I'd like to follow this direction, and start stripping the DDL support.

...please make it so.

The attached patch eliminates DDL support.

Instead of the new CREATE CUSTOM PLAN PROVIDER statement,
it adds an internal function; register_custom_scan_provider
that takes custom plan provider name and callback function
to add alternative scan path (should have a form of CustomPath)
during the query planner is finding out the cheapest path to
scan the target relation.
Also, documentation stuff is revised according to the latest
design.
Any other stuff keeps the previous design.

Comments:

1. There seems to be no reason for custom plan nodes to have MultiExec
support; I think this as an area where extensibility is extremely
unlikely to work out. The MultiExec mechanism is really only viable
between closely-cooperating nodes, like Hash and HashJoin, or
BitmapIndexScan, BitmapAnd, BitmapOr, and BitmapHeapScan; and arguably
those things could have been written as a single, more complex node.
Are we really going to want to support a custom plan that can
substitute for a Hash or BitmapAnd node? I really doubt that's very
useful.

This intends to allows a particular custom-scan provider to exchange
its internal data when multiple custom-scan node is stacked.
So, it can be considered a facility to implement closely-cooperating nodes;
both of them are managed by same custom-scan provider.
An example is gpu-accelerated version of hash-join that takes underlying
custom-scan node that will returns a hash table with gpu preferable data
structure, but should not be a part of row-by-row interface.
I believe it is valuable for some use cases, even though I couldn't find
a use-case in ctidscan example.

Color me skeptical. Please remove that part for now, and we can
revisit it when, and if, a plausible use case emerges.

3. Is it really a good idea to invoke custom scan providers for RTEs
of every type? It's pretty hard to imagine that a custom scan
provider can do anything useful with, say, RTE_VALUES. Maybe an
accelerated scan of RTE_CTE or RTE_SUBQUERY is practical somehow, but
even that feels like an awfully big stretch. At least until clear use
cases emerge, I'd be inclined to restrict this to RTE_RELATION scans
where rte->relkind != RELKIND_FOREIGN_TABLE; that is, put the logic in
set_plain_rel_pathlist() rather than set_rel_pathlist().

I'd like to agree. Indeed, it's not easy to assume a use case of
custom-logic for non-plain relations.

(We might even want to consider whether the hook in
set_plain_rel_pathlist() ought to be allowed to inject a non-custom
plan; e.g. substitute a scan of relation B for a scan of relation A.
For example, imagine that B contains all rows from A that satisfy some
predicate. This could even be useful for foreign tables; e.g.
substitute a scan of a local copy of a foreign table for a reference
to that table. But I put all of these ideas in parentheses because
they're only good ideas to the extent that they don't sidetrack us too
much.)

Hmm... It seems to me we need another infrastructure to take
a substitute scan, because add_path() is called towards a certain
RelOpInfo that is associated with the relation A.
As long as custom-scan provider "internally" redirect a request for
scan of A by substitute scan B (with taking care of all other stuff
like relation locks), I don't think we need to put some other hooks
outside from the set_plain_rel_pathlist().

OK, I see. So this would have to be implemented as some new kind of
path anyway. It might be worth allowing custom paths for scanning a
foreign table as well as a plain table, though - so any RTE_RELATION
but not other types of RTE.

It came from the discussion I had long time before during patch
reviewing of postgres_fdw. I suggested to use static table of
FdwRoutine but I got a point that says some compiler raise
error/warning to put function pointers on static initialization.
I usually use GCC only, so I'm not sure whether this argue is
right or not, even though the postgres_fdw_handler() allocates
FdwRoutine using palloc() then put function pointers for each.

That's odd, because aset.c has used static initializers since forever,
and I'm sure someone would have complained by now if there were a
problem with that usage.

--
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

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

On Sun, Aug 31, 2014 at 12:54 AM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

2014-08-29 13:33 GMT-04:00 Robert Haas <robertmhaas@gmail.com>:

Comments:

1. There seems to be no reason for custom plan nodes to have
MultiExec support; I think this as an area where extensibility is
extremely unlikely to work out. The MultiExec mechanism is really
only viable between closely-cooperating nodes, like Hash and
HashJoin, or BitmapIndexScan, BitmapAnd, BitmapOr, and
BitmapHeapScan; and arguably those things could have been written as

a single, more complex node.

Are we really going to want to support a custom plan that can
substitute for a Hash or BitmapAnd node? I really doubt that's very
useful.

This intends to allows a particular custom-scan provider to exchange
its internal data when multiple custom-scan node is stacked.
So, it can be considered a facility to implement closely-cooperating
nodes; both of them are managed by same custom-scan provider.
An example is gpu-accelerated version of hash-join that takes
underlying custom-scan node that will returns a hash table with gpu
preferable data structure, but should not be a part of row-by-row

interface.

I believe it is valuable for some use cases, even though I couldn't
find a use-case in ctidscan example.

Color me skeptical. Please remove that part for now, and we can revisit
it when, and if, a plausible use case emerges.

Now, I removed the multi-exec portion from the patch set.

Existence of this interface affects to the query execution cost so much,
so I want to revisit it as soon as possible. Also see the EXPLAIN output
on the tail of this message.

It came from the discussion I had long time before during patch
reviewing of postgres_fdw. I suggested to use static table of
FdwRoutine but I got a point that says some compiler raise
error/warning to put function pointers on static initialization.
I usually use GCC only, so I'm not sure whether this argue is right or
not, even though the postgres_fdw_handler() allocates FdwRoutine using
palloc() then put function pointers for each.

That's odd, because aset.c has used static initializers since forever, and
I'm sure someone would have complained by now if there were a problem with
that usage.

I reminded the discussion at that time. The GCC specific manner was not
static initialization itself, it was static initialization with field name.
Like:
static CustomPathMethods ctidscan_path_methods = {
.CustomName = "ctidscan",
.CreateCustomPlan = CreateCtidScanPlan,
.TextOutCustomPath = TextOutCtidScanPath,
};

Regarding to the attached three patches:
[1]: custom-path and hook It adds register_custom_path_provider() interface for registration of custom-path entrypoint. Callbacks are invoked on set_plain_rel_pathlist to offer alternative scan path on regular relations. I may need to explain the terms in use. I calls the path-node custom-path that is the previous step of population of plan-node (like custom-scan and potentially custom-join and so on). The node object created by CreateCustomPlan() is called custom-plan because it is abstraction for all the potential custom-xxx node; custom-scan is the first of all.
It adds register_custom_path_provider() interface for registration of
custom-path entrypoint. Callbacks are invoked on set_plain_rel_pathlist
to offer alternative scan path on regular relations.
I may need to explain the terms in use. I calls the path-node custom-path
that is the previous step of population of plan-node (like custom-scan
and potentially custom-join and so on). The node object created by
CreateCustomPlan() is called custom-plan because it is abstraction for
all the potential custom-xxx node; custom-scan is the first of all.

[2]: custom-scan node It adds custom-scan node support. The custom-scan node is expected to generate contents of a particular relation or sub-plan according to its custom-logic. Custom-scan provider needs to implement callbacks of CustomScanMethods and CustomExecMethods. Once a custom-scan node is populated from custom-path node, the backend calls back these methods in the planning and execution stage.
It adds custom-scan node support. The custom-scan node is expected to
generate contents of a particular relation or sub-plan according to its
custom-logic.
Custom-scan provider needs to implement callbacks of CustomScanMethods
and CustomExecMethods. Once a custom-scan node is populated from
custom-path node, the backend calls back these methods in the planning
and execution stage.

[3]: contrib/ctidscan It adds a logic to scan a base relation if WHERE clause contains inequality expression around ctid system column; that allows to skip blocks which will be unread obviously.
It adds a logic to scan a base relation if WHERE clause contains
inequality expression around ctid system column; that allows to skip
blocks which will be unread obviously.

During the refactoring, I noticed a few interface is omissible.
The backend can know which relation is the target of custom-scan node
being appeared in the plan-tree if its scanrelid > 0. So, I thought
ExplainCustomPlanTargetRel() and ExplainCustomPreScanNode() are
omissible, then removed from the patch.

Please check the attached ones.

--------
Also, regarding to the use-case of multi-exec interface.
Below is an EXPLAIN output of PG-Strom. It shows the custom GpuHashJoin has
two sub-plans; GpuScan and MultiHash.
GpuHashJoin is stacked on the GpuScan. It is a case when these nodes utilize
multi-exec interface for more efficient data exchange between the nodes.
GpuScan already keeps a data structure that is suitable to send to/recv from
GPU devices and constructed on the memory segment being DMA available.
If we have to form a tuple, pass it via row-by-row interface, then deform it,
it will become a major performance degradation in this use case.

postgres=# explain select * from t10 natural join t8 natural join t9 where x < 10;
QUERY PLAN
-----------------------------------------------------------------------------------------------
Custom (GpuHashJoin) (cost=10979.56..90064.15 rows=333 width=49)
pseudo scan tlist: 1:(t10.bid), 3:(t10.aid), 4:<t10.x>, 2:<t8.data>, 5:[t8.aid], 6:[t9.bid]
hash clause 1: ((t8.aid = t10.aid) AND (t9.bid = t10.bid))
-> Custom (GpuScan) on t10 (cost=10000.00..88831.26 rows=3333327 width=16)
Host References: aid, bid, x
Device References: x
Device Filter: (x < 10::double precision)
-> Custom (MultiHash) (cost=464.56..464.56 rows=1000 width=41)
hash keys: aid, bid
-> Hash Join (cost=60.06..464.56 rows=1000 width=41)
Hash Cond: (t9.data = t8.data)
-> Index Scan using t9_pkey on t9 (cost=0.29..357.29 rows=10000 width=37)
-> Hash (cost=47.27..47.27 rows=1000 width=37)
-> Index Scan using t8_pkey on t8 (cost=0.28..47.27 rows=1000 width=37)
Planning time: 0.810 ms
(15 rows)

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

Attachments:

pgsql-v9.5-custom-scan.part-3.v8.patchapplication/octet-stream; name=pgsql-v9.5-custom-scan.part-3.v8.patchDownload
 contrib/Makefile                       |   1 +
 contrib/ctidscan/Makefile              |  18 +
 contrib/ctidscan/ctidscan.c            | 905 +++++++++++++++++++++++++++++++++
 contrib/ctidscan/ctidscan.control      |   5 +
 contrib/ctidscan/expected/ctidscan.out | 312 ++++++++++++
 contrib/ctidscan/sql/ctidscan.sql      |  54 ++
 src/include/catalog/pg_operator.h      |   3 +
 7 files changed, 1298 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..d76a2c0
--- /dev/null
+++ b/contrib/ctidscan/ctidscan.c
@@ -0,0 +1,905 @@
+/*
+ * 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 Node *CreateCtidScanPlan(PlannerInfo *root, CustomPath *best_path);
+static void TextOutCtidScanPath(StringInfo str, const CustomPath *cpath);
+
+static Plan *InitCtidScanPlan(CustomScan *cscan,
+							  PlannerInfo *root,
+							  CustomPath *best_path,
+							  List *tlist, List *clauses);
+static void SetCtidScanPlanRef(PlannerInfo *root,
+							   CustomScan *cscan,
+							   int rtoffset);
+static void FinalizeCtidScanPlan(PlannerInfo *root,
+								 CustomScan *cscan,
+								 bool (*finalize_primnode)(),
+								 void *finalize_context);
+static Node *CreateCtidScanState(CustomScan *cscan);
+static void TextOutCtidScanPlan(StringInfo str, const CustomScan *node);
+static CustomScan *CopyCtidScanPlan(const CustomScan *from);
+static void BeginCtidScan(CustomScanState *node, EState *estate, int eflags);
+static TupleTableSlot *ExecCtidScan(CustomScanState *node);
+static void EndCtidScan(CustomScanState *node);
+static void ReScanCtidScan(CustomScanState *node);
+static void ExplainCtidScan(CustomScanState *node,
+							List *ancestors, ExplainState *es);
+
+/* custom-scan callback tables */
+static CustomPathMethods	ctidscan_path_methods = {
+	"ctidscan",				/* CustomName */
+	CreateCtidScanPlan,		/* CreateCustomPlan */
+	TextOutCtidScanPath,	/* TextOutCustomPath */
+};
+
+static CustomScanMethods	ctidscan_scan_methods = {
+	"ctidscan",				/* CustomName */
+	InitCtidScanPlan,		/* InitCustomScan */
+	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;
+}
+
+/*
+ * CreateCtidScanPlan - A method of CustomPath; that populate a custom
+ * object being delivered from CustomScan type, according to the supplied
+ * CustomPath object.
+ */
+static Node *
+CreateCtidScanPlan(PlannerInfo *root, CustomPath *best_path)
+{
+	CtidScanPath   *ctid_path = (CtidScanPath *) best_path;
+	CtidScanPlan   *ctid_scan;
+
+	ctid_scan = palloc0(sizeof(CtidScanPlan));
+	NodeSetTag(ctid_scan, T_CustomScan);
+	ctid_scan->cscan.flags = best_path->flags;
+	ctid_scan->cscan.methods = &ctidscan_scan_methods;
+	ctid_scan->ctid_quals = ctid_path->ctid_quals;
+
+	return (Node *)&ctid_scan->cscan;
+}
+
+/*
+ * InitCtidScanPlan
+ *
+ * main portion to initialize CustomScan node.
+ */
+static Plan *
+InitCtidScanPlan(CustomScan *cscan,
+				 PlannerInfo *root, CustomPath *best_path,
+				 List *tlist, List *clauses)
+{
+	CtidScanPlan   *ctid_scan = (CtidScanPlan *) cscan;
+	List		   *ctid_quals = ((CtidScanPath *)best_path)->ctid_quals;
+
+	Assert(ctid_scan->cscan.scan.scanrelid > 0);
+
+	/* 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 &ctid_scan->cscan.scan.plan;
+}
+
+/*
+ * 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 *cscan,
+				   int rtoffset)
+{
+	CtidScanPlan   *ctidscan = (CtidScanPlan *) cscan;
+	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 *cscan,
+					 bool (*finalize_primnode)(),
+					 void *finalize_context)
+{
+	CtidScanPlan   *ctid_plan = (CtidScanPlan *) cscan;
+
+	/* 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 *cscan)
+{
+	CtidScanState  *ctss = palloc0(sizeof(CtidScanState));
+
+	NodeSetTag(ctss, T_CustomScanState);
+	ctss->css.flags = cscan->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
+ */
+static void
+CtidScanAddPath(void *arg)
+{
+	customScanArg  *cscan_arg = (customScanArg *) arg;
+	PlannerInfo	   *root;
+	RangeTblEntry  *rte;
+	RelOptInfo	   *baserel;
+	char			relkind;
+	ListCell	   *lc;
+	List		   *ctid_quals = NIL;
+
+	/* ensure the context of this callback */
+	if (cscan_arg->cpp_class != CUSTOM_PATH_CLASS_SCAN)
+		return;
+	root	= cscan_arg->root;
+	rte		= cscan_arg->rte;
+	baserel	= cscan_arg->baserel;
+
+	/* all we can support is regular relations */
+	if (rte->rtekind != RTE_RELATION)
+		return;
+
+	relkind = get_rel_relkind(rte->relid);
+	if (relkind != RELKIND_RELATION &&
+		relkind != RELKIND_MATVIEW &&
+		relkind != RELKIND_TOASTVALUE)
+		return;
+
+	/* walk on the restrict info */
+	foreach (lc, baserel->baserestrictinfo)
+	{
+		RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+		List		 *temp;
+
+		if (!IsA(rinfo, RestrictInfo))
+			continue;		/* probably should never happen */
+		temp = CTidQualFromExpr((Node *) rinfo->clause, baserel->relid);
+		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);
+	}
+}
+
+/*
+ * Entrypoint of this extension
+ */
+void
+_PG_init(void)
+{
+	/* registration of this custom-plan provider for table scanning */
+	register_custom_path_provider("ctidscan",
+								  CUSTOM_PATH_CLASS_SCAN,
+								  CtidScanAddPath);
+}
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..79cd8db
--- /dev/null
+++ b/contrib/ctidscan/expected/ctidscan.out
@@ -0,0 +1,312 @@
+--
+-- Regression Tests for Custom Plan APIs
+--
+LOAD '$libdir/ctidscan';
+-- 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                              
+----------------------------------------------------------------------
+ Custom (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 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 (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 (ctidscan) on t1
+               Filter: (ctid < '(2,10)'::tid)
+               ctid quals: (ctid < '(2,10)'::tid)
+   ->  Sort
+         Sort Key: t2.ctid
+         ->  Custom (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 (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 (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)
+
+-- 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..9ac6df8
--- /dev/null
+++ b/contrib/ctidscan/sql/ctidscan.sql
@@ -0,0 +1,54 @@
+--
+-- Regression Tests for Custom Plan APIs
+--
+
+LOAD '$libdir/ctidscan';
+
+-- 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;
+
+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);
+
+-- 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.v8.patchapplication/octet-stream; name=pgsql-v9.5-custom-scan.part-2.v8.patchDownload
 doc/src/sgml/custom-plan.sgml           | 367 ++++++++++++++++++++++++++++++++
 doc/src/sgml/filelist.sgml              |   1 +
 doc/src/sgml/postgres.sgml              |   1 +
 src/backend/commands/explain.c          |  33 +++
 src/backend/executor/Makefile           |   2 +-
 src/backend/executor/execAmi.c          |  38 +++-
 src/backend/executor/execProcnode.c     |  14 ++
 src/backend/executor/nodeCustom.c       | 139 ++++++++++++
 src/backend/nodes/copyfuncs.c           |  31 +++
 src/backend/nodes/outfuncs.c            |  15 ++
 src/backend/optimizer/path/costsize.c   |   2 +-
 src/backend/optimizer/plan/createplan.c |  74 ++++++-
 src/backend/optimizer/plan/setrefs.c    |  33 ++-
 src/backend/optimizer/plan/subselect.c  |  24 +++
 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           |  35 +++
 src/include/optimizer/planmain.h        |   3 +
 21 files changed, 955 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..7e8c3e2
--- /dev/null
+++ b/doc/src/sgml/custom-plan.sgml
@@ -0,0 +1,367 @@
+<!-- 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>
+   Once a provider gets registered, the query planner calls back its entry
+   routine to ask whether it can offer an alternative path
+   to scan the referenced relations, or not.
+   If it is available to provide an alternative path, the entry routine
+   construct a <literal>CustomPath</> with cost estimation.
+   The query planner compares the alternative paths with built-in paths
+   based on 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>
+  <para>
+   A custom-path provider shall be registered using
+   <literal>register_custom_path_provider</> that takes name and class
+   of custom-path provider and a function pointer of its entry routine
+   on its argument.
+   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>
+   The provider function shall be invoked with an argument of a certain
+   data structure depending on the custom-path class being registered.
+   In this version, it can be called back only when the query planner is
+   finding out a path to scan a plain relation, not join or other special
+   nodes right now.
+   The <literal>customScanArg</> packs a set of basic, but enough,
+   information that can be used to construct <literal>CustomPath</> node.
+   If the provider can propose one or more alternative scan paths,
+   it constructs <structname>CustomPah</> object with estimated cost and
+   a set of callbacks defined at <structname>CustomPathMethods</> structure,
+   then added using <function>add_path()</>.
+  </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>
+Node *
+CreateCustomPlan(PlannerInfo *root,
+                 CustomPath *best_path);
+</programlisting>
+   It populates a <structname>CustomPlan</> (or inherited data type) node
+   according to the supplied <structname>CustomPath</> node which was
+   constructed on the custom plan handler function then chosen by the
+   query planner.
+   Only provider can know exact size of the node to be allocated, this
+   callback allocate a <structname>CustomPlan</> node with
+   <structname>CustomPlanMethods</> callbacks table 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>
+Plan      *
+InitCustomScan(CustomScan *cscan,
+               PlannerInfo *root,
+               CustomPath *best_path,
+               List *tlist,
+               List *clauses);
+</programlisting>
+   It initializes the <structname>CustomScan</> node being allocated by
+   <literal>CreateCustomPlan</> callback, if it is <literal>CustomScan</>.
+   The backend takes some common initializations prior to its invocation.
+   <literal>tlist</> and <literal>clauses</> are extracted from the path
+   node according to the usual manner, so all the provider has to do
+   is putting these members if nothing special are done.
+  </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..0e6fb86 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -722,6 +722,12 @@ ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
 			*rels_used = bms_add_member(*rels_used,
 										((Scan *) plan)->scanrelid);
 			break;
+		case T_CustomScan:
+			/* only when CustomScan scans plain relation */
+			if (((CustomScan *) plan)->scan.scanrelid > 0)
+				*rels_used = bms_add_member(*rels_used,
+									((CustomScan *) plan)->scan.scanrelid);
+			break;
 		case T_ModifyTable:
 			/* cf ExplainModifyTarget */
 			*rels_used = bms_add_member(*rels_used,
@@ -848,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;
 
@@ -936,6 +943,14 @@ ExplainNode(PlanState *planstate, List *ancestors,
 		case T_ForeignScan:
 			pname = sname = "Foreign Scan";
 			break;
+		case T_CustomScan:
+			sname = "Custom";
+			custom_name = ((CustomScan *) plan)->methods->CustomName;
+			if (custom_name)
+				pname = psprintf("Custom (%s)", custom_name);
+			else
+				pname = sname;
+			break;
 		case T_Material:
 			pname = sname = "Materialize";
 			break;
@@ -1037,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", custom_name, es);
 	}
 
 	switch (nodeTag(plan))
@@ -1084,6 +1101,10 @@ ExplainNode(PlanState *planstate, List *ancestors,
 					ExplainPropertyText("Index Name", indexname, es);
 			}
 			break;
+		case T_CustomScan:
+			if (((Scan *) plan)->scanrelid > 0)
+				ExplainScanTarget((Scan *) plan, es);
+			break;
 		case T_ModifyTable:
 			ExplainModifyTarget((ModifyTable *) plan, es);
 			break;
@@ -1353,6 +1374,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..0c330f6
--- /dev/null
+++ b/src/backend/executor/nodeCustom.c
@@ -0,0 +1,139 @@
+/* ------------------------------------------------------------------------
+ *
+ * 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;
+
+	/* 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.
+	 */
+	if (cscan->scan.scanrelid > 0)
+	{
+		Relation	heap_rel;
+
+		heap_rel = ExecOpenScanRelation(estate, cscan->scan.scanrelid, eflags);
+		css->ss.ss_currentRelation = heap_rel;
+		css->ss.ss_currentScanDesc = NULL;	/* set by provider on demand */
+		ExecInitScanTupleSlot(estate, &css->ss);
+		ExecAssignScanType(&css->ss, RelationGetDescr(heap_rel));
+		ExecAssignScanProjectionInfo(&css->ss);
+	}
+	else
+	{
+		css->ss.ss_currentRelation = NULL;
+		css->ss.ss_currentScanDesc = NULL;
+		css->ss.ss_ScanTupleSlot = NULL;
+		css->ss.ps.ps_ProjInfo = NULL;
+	}
+
+	/*
+	 * 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, if needed */
+	if (node->ss.ss_currentRelation)
+		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 aa053a0..c75fa7c 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -598,6 +598,34 @@ _copyForeignScan(const ForeignScan *from)
 }
 
 /*
+ * _copyCustomScan
+ */
+static CustomScan *
+_copyCustomScan(const CustomScan *from)
+{
+	CustomScan		   *newnode;
+	CustomScanMethods  *methods;
+
+	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 usually a pointer to static
+	 * table, so 99% of use cases don't need to have its duplication, but
+	 * it depends on extension's design. So, we takes safer design for
+	 * copyObject() implementation.
+	 */
+	methods = palloc(sizeof(CustomScanMethods));
+	memcpy(methods, from->methods, sizeof(CustomScanMethods));
+	methods->CustomName = pstrdup(from->methods->CustomName);
+	newnode->methods = methods;
+
+	return newnode;
+}
+
+/*
  * CopyJoinFields
  *
  *		This function copies the fields of the Join node.  It is used by
@@ -4012,6 +4040,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 29a12bd..f3a9d24 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");
@@ -2862,6 +2874,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 d5b0fb3..f63a3aa 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -84,7 +84,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);
@@ -1096,6 +1095,77 @@ create_custom_plan(PlannerInfo *root, CustomPath *best_path)
 		best_path->methods->CreateCustomPlan(root, best_path);
 	Assert(nodeTag(custom_plan) == best_path->path.pathtype);
 
+	if (IsA(custom_plan, CustomScan))
+	{
+		RelOptInfo	   *rel = best_path->path.parent;
+		CustomScan	   *cscan = (CustomScan *) custom_plan;
+		List		   *tlist = NIL;
+		List		   *clauses;
+
+		/*
+		 * We prefer to generate a tlist containing all the var-nodes
+		 * in order, for cheaper projection cost, if available.
+		 * use_physical_tlist() makes a centralized decision, then
+		 * build up a tlist from the physical structure of the target
+		 * relation.
+		 */
+		if (rel && rel->reloptkind == RELOPT_BASEREL)
+		{
+			Assert(rel->relid > 0);
+
+			cscan->scan.scanrelid = rel->relid;
+			if (use_physical_tlist(root, rel))
+				tlist = build_physical_tlist(root, rel);
+		}
+		/* elsewhere, we generate a tlist from the relation targetlist */
+		if (tlist == NIL)
+			tlist = build_path_tlist(root, &best_path->path);
+
+		/*
+		 * Extract the relevant restriction clauses from the parent relation.
+		 * The executor must apply all these restrictions during the scan,
+		 * except for pseudoconstants which we'll take care of below.
+		 */
+		clauses = rel->baserestrictinfo;
+
+		/*
+		 * If this is a parameterized scan, we also need to enforce all
+		 * the join clauses available from the outer relation(s).
+		 */
+		if (best_path->path.param_info)
+			clauses = list_concat(list_copy(clauses),
+								  best_path->path.param_info->ppi_clauses);
+
+		/* Sort clauses into best execution order */
+		clauses = order_qual_clauses(root, clauses);
+
+		/*
+		 * Replace outer-relation variables with nestloop params.
+		 * Note that any other clauses which is managed by extension
+		 * itself has to be handled in InitCustomPlan() method, as
+		 * built-in code doing.
+		 */
+		if (best_path->path.param_info)
+			clauses = (List *)replace_nestloop_params(root, (Node *)clauses);
+
+		/*
+		 * Let the custom-plan provider perform its final initialization
+		 * of this CustomScan (or its inheritance in actually) node
+		 * according to its own necessity.
+		 * Note that custom-scan provider may/can replace (or stack another
+		 * one on) its own custom-scan node on demand. For example, to add
+		 * Result node to handle pseudo constant using create_gating_plan().
+		 */
+		custom_plan = (Plan *)
+			cscan->methods->InitCustomScan(cscan,
+										   root,
+										   best_path,
+										   tlist,
+										   clauses);
+	}
+	else
+		elog(ERROR, "unexpected node: %d", (int)nodeTag(custom_plan));
+
 	/*
 	 * Copy cost data from Path to Plan; no need to make custom-plan
 	 * providers do this
@@ -2572,7 +2642,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 4d717df..e230dcd 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,34 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 			}
 			break;
 
+		case T_CustomScan:
+			{
+				CustomScan *cscan = (CustomScan *) plan;
+
+				/*
+				 * In case when custom-scan node actually run on a plain
+				 * relation, we apply usual preprocessing here. If custom-
+				 * scan node does not have something special in its private
+				 * field, nothing to do.
+				 * Elsewhere, core backend cannot know what is the suitable
+				 * set-reference processing, so everything is the role of
+				 * custom-scan provider.
+				 */
+				if (cscan->scan.scanrelid > 0)
+				{
+					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);
+				}
+				if (cscan->methods->SetCustomScanRef)
+					cscan->methods->SetCustomScanRef(root, cscan, rtoffset);
+			}
+			break;
+
 		case T_NestLoop:
 		case T_MergeJoin:
 		case T_HashJoin:
@@ -1060,7 +1087,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 */
@@ -1158,7 +1185,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..0aa19c9 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -2283,6 +2283,30 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
 			context.paramids = bms_add_members(context.paramids, scan_params);
 			break;
 
+		case T_CustomScan:
+			{
+				CustomScan *cscan = (CustomScan *) plan;
+
+				/*
+				 * If this custom-scan runs on a particular relation,
+				 * we adjust paramids as other scan derivered node.
+				 */
+				if (cscan->scan.scanrelid > 0)
+					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 tlist and
+				 * qual of Plan node (already done above).
+				 */
+				if (cscan->methods->FinalizeCustomScan)
+					cscan->methods->FinalizeCustomScan(root, cscan,
+													   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 6376784..f5e9dd6 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 3b9c683..57e5e1b 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"
 
 
 /* ----------------------------------------------------------------
@@ -479,6 +481,39 @@ typedef struct ForeignScan
 	bool		fsSystemCol;	/* true if any "system column" is needed */
 } ForeignScan;
 
+/* ----------------
+ *     CustomScan node
+ * ----------------
+ */
+struct CustomScanMethods;
+//struct PlannerInfo;			/* to avoid to include nodes/relation.h here */
+
+typedef struct CustomScan
+{
+	Scan		scan;
+	uint32		flags;	/* mask of CUSTOMPATH_* flags defined in relation.h*/
+	const struct CustomScanMethods *methods;
+} CustomScan;
+
+typedef struct CustomScanMethods
+{
+	const char *CustomName;
+	Plan	  *(*InitCustomScan)(CustomScan *cscan,
+								 struct PlannerInfo *root,
+								 struct CustomPath *best_path,
+								 List *tlist,
+								 List *clauses);
+	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;
 
 /*
  * ==========
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 4504250..ace3bef 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.v8.patchapplication/octet-stream; name=pgsql-v9.5-custom-scan.part-1.v8.patchDownload
 src/backend/nodes/outfuncs.c            | 14 +++++++
 src/backend/optimizer/path/allpaths.c   |  3 ++
 src/backend/optimizer/plan/createplan.c | 32 ++++++++++++++++
 src/backend/optimizer/util/pathnode.c   | 68 +++++++++++++++++++++++++++++++++
 src/include/nodes/nodes.h               |  2 +
 src/include/nodes/relation.h            | 30 +++++++++++++++
 src/include/optimizer/pathnode.h        | 26 +++++++++++++
 7 files changed, 175 insertions(+)

diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e686a6c..29a12bd 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");
@@ -3059,6 +3070,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..d5b0fb3 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -77,6 +77,7 @@ 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 Plan *create_custom_plan(PlannerInfo *root, CustomPath *best_path);
 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,
@@ -261,6 +262,9 @@ create_plan_recurse(PlannerInfo *root, Path *best_path)
 			plan = create_unique_plan(root,
 									  (UniquePath *) best_path);
 			break;
+		case T_CustomScan:
+			plan = create_custom_plan(root, (CustomPath *) best_path);
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) best_path->pathtype);
@@ -1072,6 +1076,34 @@ 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 'scan_clauses' and targetlist 'tlist'.
+ */
+static Plan *
+create_custom_plan(PlannerInfo *root, CustomPath *best_path)
+{
+	Plan		   *custom_plan;
+
+	/*
+	 * Create a CustomScan (or its inheritance) according to
+	 * the supplied CustomPath.
+	 */
+	Assert(best_path->path.pathtype == T_CustomScan);
+	custom_plan = (Plan *)
+		best_path->methods->CreateCustomPlan(root, best_path);
+	Assert(nodeTag(custom_plan) == best_path->path.pathtype);
+
+	/*
+	 * Copy cost data from Path to Plan; no need to make custom-plan
+	 * providers do this
+	 */
+	copy_path_costsize(custom_plan, &best_path->path);
+
+	return custom_plan;
+}
 
 /*****************************************************************************
  *
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 319e8b2..b723bdd 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,70 @@ reparameterize_path(PlannerInfo *root, Path *path,
 	}
 	return NULL;
 }
+
+/*****************************************************************************
+ *     creation of custom-plan paths
+ *****************************************************************************/
+typedef struct
+{
+	const char	   *cpp_name;
+	uint32			cpp_class;
+	add_custom_path_type cpp_callback;
+} custom_path_provider_info;
+
+static List	   *custom_path_providers = NIL;
+
+/*
+ * register_custom_path_provider
+ *
+ * It registers an entrypoint of custom-path providers.
+ * The callback function is expected to construct CustomPath node to run
+ * alternative logic instead of the existing node, if extension can do.
+ */
+void
+register_custom_path_provider(const char *cpp_name,
+							  int cpp_class,
+							  add_custom_path_type callback)
+{
+	custom_path_provider_info *cpp_info;
+	MemoryContext		oldcxt;
+
+	oldcxt = MemoryContextSwitchTo(TopMemoryContext);
+	cpp_info = palloc(sizeof(custom_path_provider_info));
+	cpp_info->cpp_name = pstrdup(cpp_name);
+	cpp_info->cpp_class = cpp_class;
+	cpp_info->cpp_callback = callback;
+
+	custom_path_providers = lappend(custom_path_providers, cpp_info);
+
+	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)
+{
+	customScanArg	sarg;
+	ListCell	   *cell;
+
+	sarg.cpp_class = CUSTOM_PATH_CLASS_SCAN;
+	sarg.root = root;
+	sarg.baserel = baserel;
+	sarg.rte = rte;
+
+	foreach (cell, custom_path_providers)
+	{
+		custom_path_provider_info  *cpp_info = lfirst(cell);
+
+		if (cpp_info->cpp_class == CUSTOM_PATH_CLASS_SCAN)
+			cpp_info->cpp_callback(&sarg);
+	}
+}
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index a031b88..6376784 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/relation.h b/src/include/nodes/relation.h
index dacbe9c..188cc54 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"
@@ -881,6 +882,35 @@ 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;
+
+#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;
+	Node   *(*CreateCustomPlan)(PlannerInfo *root,
+								CustomPath *best_path);
+	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..b6c6284 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -129,6 +129,32 @@ extern Path *reparameterize_path(PlannerInfo *root, Path *path,
 					double loop_count);
 
 /*
+ * Interface definition of custom-scan providers
+ */
+#define CUSTOM_PATH_CLASS_SCAN		's'
+/* XXX - Other custom class paths shall be added in the later version */
+
+/*
+ * customScanArg - A set of arguments for relation scan.
+ */
+typedef struct {
+	int				cpp_class;	/* = CUSTOM_PATH_CLASS_SCAN */
+	PlannerInfo	   *root;
+	RelOptInfo	   *baserel;
+	RangeTblEntry  *rte;
+} customScanArg;
+
+typedef void (*add_custom_path_type)(void *arg);
+
+extern void register_custom_path_provider(const char *cpp_name,
+										  int cpp_class,
+										  add_custom_path_type callback);
+
+extern void create_customscan_paths(PlannerInfo *root,
+									RelOptInfo *baserel,
+									RangeTblEntry *rte);
+
+/*
  * prototypes for relnode.c
  */
 extern void setup_simple_rel_arrays(PlannerInfo *root);
#28Robert Haas
robertmhaas@gmail.com
In reply to: Kouhei Kaigai (#27)

On Thu, Sep 4, 2014 at 7:57 PM, Kouhei Kaigai <kaigai@ak.jp.nec.com> wrote:

Regarding to the attached three patches:
[1] custom-path and hook
It adds register_custom_path_provider() interface for registration of
custom-path entrypoint. Callbacks are invoked on set_plain_rel_pathlist
to offer alternative scan path on regular relations.
I may need to explain the terms in use. I calls the path-node custom-path
that is the previous step of population of plan-node (like custom-scan
and potentially custom-join and so on). The node object created by
CreateCustomPlan() is called custom-plan because it is abstraction for
all the potential custom-xxx node; custom-scan is the first of all.

I don't think it's a good thing that add_custom_path_type is declared
as void (*)(void *) rather than having a real type. I suggest we add
the path-creation callback function to CustomPlanMethods instead, like
this:

void (*CreateCustomScanPath)(PlannerInfo *root, RelOptInfo *baserel,
RangeTblEntry *rte);

Then, register_custom_path_provider() can just take CustomPathMethods
* as an argument; and create_customscan_paths can just walk the list
of CustomPlanMethods objects and call CreateCustomScanPath for each
one where that is non-NULL. This conflates the path generation
mechanism with the type of path getting generated a little bit, but I
don't see any real downside to that. I don't see a reason why you'd
ever want two different providers to offer the same type of
custompath.

Don't the changes to src/backend/optimizer/plan/createplan.c belong in patch #2?

[2] custom-scan node
It adds custom-scan node support. The custom-scan node is expected to
generate contents of a particular relation or sub-plan according to its
custom-logic.
Custom-scan provider needs to implement callbacks of CustomScanMethods
and CustomExecMethods. Once a custom-scan node is populated from
custom-path node, the backend calls back these methods in the planning
and execution stage.

It looks to me like this patch is full of holdovers from its earlier
life as a more-generic CustomPlan node. In particular, it contains
numerous defenses against the case where scanrelid != 0. These are
confusingly written as scanrelid > 0, but I think really they're just
bogus altogether: if this is specifically a CustomScan, not a
CustomPlan, then the relid should always be filled in. Please
consider what can be simplified here.

The comment in _copyCustomScan looks bogus to me. I think we should
*require* a static method table.

In create_custom_plan, you if (IsA(custom_plan, CustomScan)) { lots of
stuff; } else elog(ERROR, ...). I think it would be clearer to write
if (!IsA(custom_plan, CustomScan)) elog(ERROR, ...); lots of stuff;

Also, regarding to the use-case of multi-exec interface.
Below is an EXPLAIN output of PG-Strom. It shows the custom GpuHashJoin has
two sub-plans; GpuScan and MultiHash.
GpuHashJoin is stacked on the GpuScan. It is a case when these nodes utilize
multi-exec interface for more efficient data exchange between the nodes.
GpuScan already keeps a data structure that is suitable to send to/recv from
GPU devices and constructed on the memory segment being DMA available.
If we have to form a tuple, pass it via row-by-row interface, then deform it,
it will become a major performance degradation in this use case.

postgres=# explain select * from t10 natural join t8 natural join t9 where x < 10;
QUERY PLAN
-----------------------------------------------------------------------------------------------
Custom (GpuHashJoin) (cost=10979.56..90064.15 rows=333 width=49)
pseudo scan tlist: 1:(t10.bid), 3:(t10.aid), 4:<t10.x>, 2:<t8.data>, 5:[t8.aid], 6:[t9.bid]
hash clause 1: ((t8.aid = t10.aid) AND (t9.bid = t10.bid))
-> Custom (GpuScan) on t10 (cost=10000.00..88831.26 rows=3333327 width=16)
Host References: aid, bid, x
Device References: x
Device Filter: (x < 10::double precision)
-> Custom (MultiHash) (cost=464.56..464.56 rows=1000 width=41)
hash keys: aid, bid
-> Hash Join (cost=60.06..464.56 rows=1000 width=41)
Hash Cond: (t9.data = t8.data)
-> Index Scan using t9_pkey on t9 (cost=0.29..357.29 rows=10000 width=37)
-> Hash (cost=47.27..47.27 rows=1000 width=37)
-> Index Scan using t8_pkey on t8 (cost=0.28..47.27 rows=1000 width=37)
Planning time: 0.810 ms
(15 rows)

Why can't the Custom(GpuHashJoin) node build the hash table internally
instead of using a separate node?

Also, for this patch we are only considering custom scan. Custom join
is another patch. We don't need to provide infrastructure for that
patch in this one.

--
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

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

On Thu, Sep 4, 2014 at 7:57 PM, Kouhei Kaigai <kaigai@ak.jp.nec.com> wrote:

Regarding to the attached three patches:
[1] custom-path and hook
It adds register_custom_path_provider() interface for registration
of custom-path entrypoint. Callbacks are invoked on
set_plain_rel_pathlist to offer alternative scan path on regular

relations.

I may need to explain the terms in use. I calls the path-node
custom-path that is the previous step of population of plan-node
(like custom-scan and potentially custom-join and so on). The node
object created by
CreateCustomPlan() is called custom-plan because it is abstraction
for all the potential custom-xxx node; custom-scan is the first of all.

I don't think it's a good thing that add_custom_path_type is declared
as void (*)(void *) rather than having a real type. I suggest we add
the path-creation callback function to CustomPlanMethods instead, like
this:

void (*CreateCustomScanPath)(PlannerInfo *root, RelOptInfo *baserel,
RangeTblEntry *rte);

Then, register_custom_path_provider() can just take CustomPathMethods
* as an argument; and create_customscan_paths can just walk the list
of CustomPlanMethods objects and call CreateCustomScanPath for each
one where that is non-NULL. This conflates the path generation
mechanism with the type of path getting generated a little bit, but I
don't see any real downside to that. I don't see a reason why you'd
ever want two different providers to offer the same type of custompath.

It seems to me the design you suggested is smarter than the original one.
The first patch was revised according to the design.

Don't the changes to src/backend/optimizer/plan/createplan.c belong in
patch #2?

The borderline between #1 and #2 is little bit bogus. So, I moved most of
portion into #1, however, invocation of InitCustomScan (that is a callback
in CustomPlanMethod) in create_custom_plan() is still in #2.

[2] custom-scan node
It adds custom-scan node support. The custom-scan node is expected
to generate contents of a particular relation or sub-plan according
to its custom-logic.
Custom-scan provider needs to implement callbacks of
CustomScanMethods and CustomExecMethods. Once a custom-scan node is
populated from custom-path node, the backend calls back these
methods in the planning and execution stage.

It looks to me like this patch is full of holdovers from its earlier
life as a more-generic CustomPlan node. In particular, it contains
numerous defenses against the case where scanrelid != 0. These are
confusingly written as scanrelid > 0, but I think really they're just bogus altogether:
if this is specifically a CustomScan, not a CustomPlan, then the relid
should always be filled in. Please consider what can be simplified here.

OK, I revised. Now custom-scan assumes it has a particular valid relation
to be scanned, so no code path with scanrelid == 0 at this moment.

Let us revisit this scenario when custom-scan replaces relation-joins.
In this case, custom-scan will not be associated with a particular base-
relation, thus it needs to admit a custom-scan node with scanrelid == 0.

The comment in _copyCustomScan looks bogus to me. I think we should
*require* a static method table.

OK, it was fixed to copy the pointer of function table; not table itself.

In create_custom_plan, you if (IsA(custom_plan, CustomScan)) { lots of
stuff; } else elog(ERROR, ...). I think it would be clearer to write
if (!IsA(custom_plan, CustomScan)) elog(ERROR, ...); lots of stuff;

Fixed.

Also, regarding to the use-case of multi-exec interface.
Below is an EXPLAIN output of PG-Strom. It shows the custom
GpuHashJoin has two sub-plans; GpuScan and MultiHash.
GpuHashJoin is stacked on the GpuScan. It is a case when these nodes
utilize multi-exec interface for more efficient data exchange
between

the nodes.

GpuScan already keeps a data structure that is suitable to send
to/recv from GPU devices and constructed on the memory segment being
DMA

available.

If we have to form a tuple, pass it via row-by-row interface, then
deform it, it will become a major performance degradation in this
use

case.

postgres=# explain select * from t10 natural join t8 natural join t9
where

x < 10;

QUERY PLAN

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

------------------------- Custom (GpuHashJoin)
(cost=10979.56..90064.15 rows=333 width=49)
pseudo scan tlist: 1:(t10.bid), 3:(t10.aid), 4:<t10.x>,
2:<t8.data>,

5:[t8.aid], 6:[t9.bid]

hash clause 1: ((t8.aid = t10.aid) AND (t9.bid = t10.bid))
-> Custom (GpuScan) on t10 (cost=10000.00..88831.26
rows=3333327

width=16)

Host References: aid, bid, x
Device References: x
Device Filter: (x < 10::double precision)
-> Custom (MultiHash) (cost=464.56..464.56 rows=1000 width=41)
hash keys: aid, bid
-> Hash Join (cost=60.06..464.56 rows=1000 width=41)
Hash Cond: (t9.data = t8.data)
-> Index Scan using t9_pkey on t9
(cost=0.29..357.29

rows=10000 width=37)

-> Hash (cost=47.27..47.27 rows=1000 width=37)
-> Index Scan using t8_pkey on t8
(cost=0.28..47.27 rows=1000 width=37) Planning time: 0.810 ms
(15 rows)

Why can't the Custom(GpuHashJoin) node build the hash table internally
instead of using a separate node?

It's possible, however, it prevents to check sub-plans using EXPLAIN if we
manage inner-plans internally. So, I'd like to have a separate node being
connected to the inner-plan.

Also, for this patch we are only considering custom scan. Custom join
is another patch. We don't need to provide infrastructure for that
patch in this one.

OK, let me revisit it on the next stage, with functionalities above.

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

Attachments:

pgsql-v9.5-custom-scan.part-3.v9.patchapplication/octet-stream; name=pgsql-v9.5-custom-scan.part-3.v9.patchDownload
 contrib/Makefile                       |   1 +
 contrib/ctidscan/Makefile              |  18 +
 contrib/ctidscan/ctidscan.c            | 896 +++++++++++++++++++++++++++++++++
 contrib/ctidscan/ctidscan.control      |   5 +
 contrib/ctidscan/expected/ctidscan.out | 312 ++++++++++++
 contrib/ctidscan/sql/ctidscan.sql      |  54 ++
 src/include/catalog/pg_operator.h      |   3 +
 7 files changed, 1289 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..f9120aa
--- /dev/null
+++ b/contrib/ctidscan/ctidscan.c
@@ -0,0 +1,896 @@
+/*
+ * 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 Node *CreateCtidScanPlan(PlannerInfo *root, CustomPath *cpath);
+static void TextOutCtidScanPath(StringInfo str, const CustomPath *cpath);
+
+static Plan *InitCtidScanPlan(CustomScan *custom_plan,
+							  PlannerInfo *root, CustomPath *cpath,
+							  List *tlist, List *clauses);
+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 */
+	CreateCtidScanPlan,		/* CreateCustomPlan */
+	TextOutCtidScanPath,	/* TextOutCustomPath */
+};
+
+static CustomScanMethods	ctidscan_scan_methods = {
+	"ctidscan",				/* CustomName */
+	InitCtidScanPlan,		/* InitCustomScan */
+	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 Node *
+CreateCtidScanPlan(PlannerInfo *root, CustomPath *best_path)
+{
+	CtidScanPath   *ctid_path = (CtidScanPath *) best_path;
+	CtidScanPlan   *ctid_scan;
+
+	ctid_scan = palloc0(sizeof(CtidScanPlan));
+	NodeSetTag(ctid_scan, T_CustomScan);
+	ctid_scan->cscan.flags = best_path->flags;
+	ctid_scan->cscan.methods = &ctidscan_scan_methods;
+	ctid_scan->ctid_quals = ctid_path->ctid_quals;
+
+	return (Node *)&ctid_scan->cscan;
+}
+
+/*
+ * InitCtidScanPlan
+ *
+ * main portion to initialize CustomScan node.
+ */
+static Plan *
+InitCtidScanPlan(CustomScan *custom_plan,
+				 PlannerInfo *root, CustomPath *best_path,
+				 List *tlist, List *clauses)
+{
+	CtidScanPlan   *ctid_scan = (CtidScanPlan *)custom_plan;
+	List		   *ctid_quals = ((CtidScanPath *)best_path)->ctid_quals;
+
+	Assert(ctid_scan->cscan.scan.scanrelid > 0);
+
+	/* 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 &ctid_scan->cscan.scan.plan;
+}
+
+/*
+ * 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..79cd8db
--- /dev/null
+++ b/contrib/ctidscan/expected/ctidscan.out
@@ -0,0 +1,312 @@
+--
+-- Regression Tests for Custom Plan APIs
+--
+LOAD '$libdir/ctidscan';
+-- 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                              
+----------------------------------------------------------------------
+ Custom (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 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 (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 (ctidscan) on t1
+               Filter: (ctid < '(2,10)'::tid)
+               ctid quals: (ctid < '(2,10)'::tid)
+   ->  Sort
+         Sort Key: t2.ctid
+         ->  Custom (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 (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 (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)
+
+-- 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..9ac6df8
--- /dev/null
+++ b/contrib/ctidscan/sql/ctidscan.sql
@@ -0,0 +1,54 @@
+--
+-- Regression Tests for Custom Plan APIs
+--
+
+LOAD '$libdir/ctidscan';
+
+-- 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;
+
+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);
+
+-- 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.v9.patchapplication/octet-stream; name=pgsql-v9.5-custom-scan.part-2.v9.patchDownload
 doc/src/sgml/custom-plan.sgml           | 370 ++++++++++++++++++++++++++++++++
 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 |  18 +-
 src/backend/optimizer/plan/setrefs.c    |  33 ++-
 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           |  25 +++
 src/include/optimizer/planmain.h        |   3 +
 21 files changed, 864 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..55ec64f
--- /dev/null
+++ b/doc/src/sgml/custom-plan.sgml
@@ -0,0 +1,370 @@
+<!-- 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>
+Node *
+CreateCustomPlan(PlannerInfo *root,
+                 CustomPath *best_path);
+</programlisting>
+   It populates a <structname>CustomPlan</> (or inherited data type) node
+   according to the supplied <structname>CustomPath</> node which was
+   constructed on the custom plan handler function then chosen by the
+   query planner.
+   Only provider can know exact size of the node to be allocated, this
+   callback allocate a <structname>CustomPlan</> node with
+   <structname>CustomPlanMethods</> callbacks table 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>
+Plan      *
+InitCustomScan(CustomScan *cscan,
+               PlannerInfo *root,
+               CustomPath *best_path,
+               List *tlist,
+               List *clauses);
+</programlisting>
+   It initializes the <structname>CustomScan</> node being allocated by
+   <literal>CreateCustomPlan</> callback, if it is <literal>CustomScan</>.
+   The backend takes some common initializations prior to its invocation.
+   <literal>tlist</> and <literal>clauses</> are extracted from the path
+   node according to the usual manner, so all the provider has to do
+   is putting these members if nothing special are done.
+  </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..579479d 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";
+			custom_name = ((CustomScan *) plan)->methods->CustomName;
+			if (custom_name)
+				pname = psprintf("Custom (%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", 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 aa053a0..c31f065 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
@@ -4012,6 +4035,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 29a12bd..f3a9d24 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");
@@ -2862,6 +2874,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 51b2fab..bc2b12e 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -84,7 +84,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);
@@ -1158,6 +1157,21 @@ create_custom_plan(PlannerInfo *root, CustomPath *best_path)
 		clauses = (List *)replace_nestloop_params(root, (Node *)clauses);
 
 	/*
+	 * Let the custom-plan provider perform its final initialization
+	 * of this CustomScan (or its inheritance in actually) node
+	 * according to its own necessity.
+	 * Note that custom-scan provider may/can replace (or stack another
+	 * one on) its own custom-scan node on demand. For example, to add
+	 * Result node to handle pseudo constant using create_gating_plan().
+	 */
+	custom_plan = (Plan *)
+		custom_scan->methods->InitCustomScan(custom_scan,
+											 root,
+											 best_path,
+											 tlist,
+											 clauses);
+
+	/*
 	 * Copy cost data from Path to Plan; no need to make custom-plan
 	 * providers do this
 	 */
@@ -2633,7 +2647,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 4d717df..e230dcd 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,34 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 			}
 			break;
 
+		case T_CustomScan:
+			{
+				CustomScan *cscan = (CustomScan *) plan;
+
+				/*
+				 * In case when custom-scan node actually run on a plain
+				 * relation, we apply usual preprocessing here. If custom-
+				 * scan node does not have something special in its private
+				 * field, nothing to do.
+				 * Elsewhere, core backend cannot know what is the suitable
+				 * set-reference processing, so everything is the role of
+				 * custom-scan provider.
+				 */
+				if (cscan->scan.scanrelid > 0)
+				{
+					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);
+				}
+				if (cscan->methods->SetCustomScanRef)
+					cscan->methods->SetCustomScanRef(root, cscan, rtoffset);
+			}
+			break;
+
 		case T_NestLoop:
 		case T_MergeJoin:
 		case T_HashJoin:
@@ -1060,7 +1087,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 */
@@ -1158,7 +1185,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 6376784..f5e9dd6 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 c62d219..696f996 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"
 
 
 /* ----------------------------------------------------------------
@@ -483,12 +485,35 @@ 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;
+	Plan	  *(*InitCustomScan)(CustomScan *cscan,
+								 struct PlannerInfo *root,
+								 struct CustomPath *best_path,
+								 List *tlist,
+								 List *clauses);
+	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 4504250..ace3bef 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.v9.patchapplication/octet-stream; name=pgsql-v9.5-custom-scan.part-1.v9.patchDownload
 src/backend/nodes/outfuncs.c            | 14 +++++
 src/backend/optimizer/path/allpaths.c   |  3 ++
 src/backend/optimizer/plan/createplan.c | 93 +++++++++++++++++++++++++++++++++
 src/backend/optimizer/util/pathnode.c   | 50 ++++++++++++++++++
 src/include/nodes/nodes.h               |  2 +
 src/include/nodes/plannodes.h           |  9 ++++
 src/include/nodes/relation.h            | 33 ++++++++++++
 src/include/optimizer/pathnode.h        |  9 ++++
 8 files changed, 213 insertions(+)

diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e686a6c..29a12bd 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");
@@ -3059,6 +3070,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..51b2fab 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -77,6 +77,7 @@ 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 Plan *create_custom_plan(PlannerInfo *root, CustomPath *best_path);
 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,
@@ -261,6 +262,9 @@ create_plan_recurse(PlannerInfo *root, Path *best_path)
 			plan = create_unique_plan(root,
 									  (UniquePath *) best_path);
 			break;
+		case T_CustomScan:
+			plan = create_custom_plan(root, (CustomPath *) best_path);
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) best_path->pathtype);
@@ -1072,6 +1076,95 @@ 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 'scan_clauses' and targetlist 'tlist'.
+ */
+static Plan *
+create_custom_plan(PlannerInfo *root, CustomPath *best_path)
+{
+	Plan		   *custom_plan;
+	RelOptInfo	   *rel;
+	CustomScan	   *custom_scan;
+	List		   *tlist;
+	List		   *clauses;
+
+	/*
+	 * Create a CustomScan (or its inheritance) according to
+	 * the supplied CustomPath.
+	 */
+	Assert(best_path->path.pathtype == T_CustomScan);
+	custom_plan = (Plan *)
+		best_path->methods->CreateCustomPlan(root, best_path);
+	Assert(nodeTag(custom_plan) == best_path->path.pathtype);
+
+	/*
+	 * Right now, only CustomScan node which is associated with a particular
+	 * base relation is supported "custom-plan". A series of initialization
+	 * below also assume the CustomScan node provides an alternative logic
+	 * to scan these relations, and applies common initialization for this
+	 * workloads.
+	 */
+	if (!IsA(custom_plan, CustomScan))
+		elog(ERROR, "unexpected node: %d", (int)nodeTag(custom_plan));
+
+	custom_scan = (CustomScan *) custom_plan;
+	rel = best_path->path.parent;
+	Assert(rel && rel->reloptkind == RELOPT_BASEREL);
+	custom_scan->scan.scanrelid = rel->relid;
+
+	/*
+	 * We prefer to generate a tlist containing all the var-nodes
+	 * in order, for cheaper projection cost, if available.
+	 * use_physical_tlist() makes a centralized decision, then
+	 * build up a tlist from the physical structure of the target
+	 * relation.
+	 */
+	if (use_physical_tlist(root, rel))
+		tlist = build_physical_tlist(root, rel);
+	else
+		tlist = build_path_tlist(root, &best_path->path);
+
+	/*
+	 * Extract the relevant restriction clauses from the parent relation.
+	 * The executor must apply all these restrictions during the scan,
+	 * except for pseudoconstants which we'll take care of below.
+	 */
+	clauses = rel->baserestrictinfo;	
+
+	/*
+     * If this is a parameterized scan, we also need to enforce all
+	 * the join clauses available from the outer relation(s).
+	 */
+	if (best_path->path.param_info)
+		clauses = list_concat(list_copy(clauses),
+							  best_path->path.param_info->ppi_clauses);
+
+	/*
+	 * Sort clauses into the best execution order, although custom-scan
+	 * provider can reorder them again.
+	 */
+	clauses = order_qual_clauses(root, clauses);
+
+	/*
+	 * Replace outer-relation variables with nestloop params.
+	 * Note that any other clauses which is managed by extension
+	 * itself has to be handled in InitCustomPlan() method, as
+	 * built-in code doing.
+	 */
+	if (best_path->path.param_info)
+		clauses = (List *)replace_nestloop_params(root, (Node *)clauses);
+
+	/*
+	 * Copy cost data from Path to Plan; no need to make custom-plan
+	 * providers do this
+	 */
+	copy_path_costsize(custom_plan, &best_path->path);
+
+	return custom_plan;
+}
 
 /*****************************************************************************
  *
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 a031b88..6376784 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 3b9c683..c62d219 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -479,6 +479,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 dacbe9c..b078f4d 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"
@@ -881,6 +882,38 @@ 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;
+
+#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);
+	Node   *(*CreateCustomPlan)(PlannerInfo *root,
+								CustomPath *best_path);
+	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);
#30Robert Haas
robertmhaas@gmail.com
In reply to: Kouhei Kaigai (#29)

On Thu, Sep 11, 2014 at 11:24 AM, Kouhei Kaigai <kaigai@ak.jp.nec.com> wrote:

Don't the changes to src/backend/optimizer/plan/createplan.c belong in
patch #2?

The borderline between #1 and #2 is little bit bogus. So, I moved most of
portion into #1, however, invocation of InitCustomScan (that is a callback
in CustomPlanMethod) in create_custom_plan() is still in #2.

Eh, create_custom_scan() certainly looks like it is in #1 from here,
or at least part of it is. It calculates tlist and clauses and then
does nothing with them. That clearly can't be the right division.

I think it would make sense to have create_custom_scan() compute tlist
and clauses first, and then pass those to CreateCustomPlan(). Then
you don't need a separate InitCustomScan() - which is misnamed anyway,
since it has nothing to do with ExecInitCustomScan().

OK, I revised. Now custom-scan assumes it has a particular valid relation
to be scanned, so no code path with scanrelid == 0 at this moment.

Let us revisit this scenario when custom-scan replaces relation-joins.
In this case, custom-scan will not be associated with a particular base-
relation, thus it needs to admit a custom-scan node with scanrelid == 0.

Yeah, I guess the question there is whether we'll want let CustomScan
have scanrelid == 0 or require that CustomJoin be used there instead.

Why can't the Custom(GpuHashJoin) node build the hash table internally
instead of using a separate node?

It's possible, however, it prevents to check sub-plans using EXPLAIN if we
manage inner-plans internally. So, I'd like to have a separate node being
connected to the inner-plan.

Isn't that just a matter of letting the EXPLAIN code print more stuff?
Why can't it?

--
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

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

On Thu, Sep 11, 2014 at 11:24 AM, Kouhei Kaigai <kaigai@ak.jp.nec.com>
wrote:

Don't the changes to src/backend/optimizer/plan/createplan.c belong
in patch #2?

The borderline between #1 and #2 is little bit bogus. So, I moved most
of portion into #1, however, invocation of InitCustomScan (that is a
callback in CustomPlanMethod) in create_custom_plan() is still in #2.

Eh, create_custom_scan() certainly looks like it is in #1 from here, or
at least part of it is. It calculates tlist and clauses and then does
nothing with them. That clearly can't be the right division.

I think it would make sense to have create_custom_scan() compute tlist and
clauses first, and then pass those to CreateCustomPlan(). Then you don't
need a separate InitCustomScan() - which is misnamed anyway, since it has
nothing to do with ExecInitCustomScan().

The only reason why I put separate hooks here is, create_custom_scan() needs
to know exact size of the CustomScan node (including private fields), however,
it is helpful for extensions to kick its callback to initialize the node
next to the common initialization stuff.

If we have a static field to inform exact size of the data-type inherited
from the CustomScan in CustomPathMethod, it may be able to eliminate the
CreateCustomPlan(). One downside is, extension needs to register multiple
CustomPath table for each custom-plan node to be populated later.
So, my preference is the current design rather than static approach.

Regarding to the naming, how about GetCustomScan() instead of InitCustomScan()?
It follows the manner in create_foreignscan_plan().

OK, I revised. Now custom-scan assumes it has a particular valid
relation to be scanned, so no code path with scanrelid == 0 at this moment.

Let us revisit this scenario when custom-scan replaces relation-joins.
In this case, custom-scan will not be associated with a particular
base- relation, thus it needs to admit a custom-scan node with scanrelid

== 0.

Yeah, I guess the question there is whether we'll want let CustomScan have
scanrelid == 0 or require that CustomJoin be used there instead.

Right now, I cannot imagine a use case that requires individual CustomJoin
node because CustomScan with scanrelid==0 (that performs like custom-plan
rather than custom-scan in actually) is sufficient.

If a CustomScan gets chosen instead of built-in join logics, it shall looks
like a relation scan on the virtual one that is consists of two underlying
relation. Callbacks of the CustomScan has a responsibility to join underlying
relations; that is invisible from the core executor.

It seems to me CustomScan with scanrelid==0 is sufficient to implement
an alternative logic on relation joins, don't need an individual node
from the standpoint of executor.

Why can't the Custom(GpuHashJoin) node build the hash table
internally instead of using a separate node?

It's possible, however, it prevents to check sub-plans using EXPLAIN
if we manage inner-plans internally. So, I'd like to have a separate
node being connected to the inner-plan.

Isn't that just a matter of letting the EXPLAIN code print more stuff?
Why can't it?

My GpuHashJoin takes multiple relations to load them a hash-table.
On the other hand, Plan node can have two underlying relations at most
(inner/outer). Outer-side is occupied by the larger relation, so it
needs to make multiple relations visible using inner-branch.
If CustomScan can has a list of multiple underlying plan-nodes, like
Append node, it can represent the structure above in straightforward
way, but I'm uncertain which is the better design.

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

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

On Thu, Sep 11, 2014 at 8:40 PM, Kouhei Kaigai <kaigai@ak.jp.nec.com> wrote:

On Thu, Sep 11, 2014 at 11:24 AM, Kouhei Kaigai <kaigai@ak.jp.nec.com>
wrote:

Don't the changes to src/backend/optimizer/plan/createplan.c belong
in patch #2?

The borderline between #1 and #2 is little bit bogus. So, I moved most
of portion into #1, however, invocation of InitCustomScan (that is a
callback in CustomPlanMethod) in create_custom_plan() is still in #2.

Eh, create_custom_scan() certainly looks like it is in #1 from here, or
at least part of it is. It calculates tlist and clauses and then does
nothing with them. That clearly can't be the right division.

I think it would make sense to have create_custom_scan() compute tlist and
clauses first, and then pass those to CreateCustomPlan(). Then you don't
need a separate InitCustomScan() - which is misnamed anyway, since it has
nothing to do with ExecInitCustomScan().

The only reason why I put separate hooks here is, create_custom_scan() needs
to know exact size of the CustomScan node (including private fields), however,
it is helpful for extensions to kick its callback to initialize the node
next to the common initialization stuff.

Why does it need to know that? I don't see that it's doing anything
that requires knowing the size of that node, and if it is, I think it
shouldn't be. That should get delegated to the callback provided by
the custom plan provider.

Regarding to the naming, how about GetCustomScan() instead of InitCustomScan()?
It follows the manner in create_foreignscan_plan().

I guess that's a bit better, but come to think of it, I'd really like
to avoid baking in the assumption that the custom path provider has to
return any particular type of plan node. A good start would be to
give it a name that doesn't imply that - e.g. PlanCustomPath().

OK, I revised. Now custom-scan assumes it has a particular valid
relation to be scanned, so no code path with scanrelid == 0 at this moment.

Let us revisit this scenario when custom-scan replaces relation-joins.
In this case, custom-scan will not be associated with a particular
base- relation, thus it needs to admit a custom-scan node with scanrelid

== 0.

Yeah, I guess the question there is whether we'll want let CustomScan have
scanrelid == 0 or require that CustomJoin be used there instead.

Right now, I cannot imagine a use case that requires individual CustomJoin
node because CustomScan with scanrelid==0 (that performs like custom-plan
rather than custom-scan in actually) is sufficient.

If a CustomScan gets chosen instead of built-in join logics, it shall looks
like a relation scan on the virtual one that is consists of two underlying
relation. Callbacks of the CustomScan has a responsibility to join underlying
relations; that is invisible from the core executor.

It seems to me CustomScan with scanrelid==0 is sufficient to implement
an alternative logic on relation joins, don't need an individual node
from the standpoint of executor.

That's valid logic, but it's not the only way to do it. If we have
CustomScan and CustomJoin, either of them will require some adaption
to handle this case. We can either allow a custom scan that isn't
scanning any particular relation (i.e. scanrelid == 0), or we can
allow a custom join that has no children. I don't know which way will
come out cleaner, and I think it's good to leave that decision to one
side for now.

Why can't the Custom(GpuHashJoin) node build the hash table
internally instead of using a separate node?

It's possible, however, it prevents to check sub-plans using EXPLAIN
if we manage inner-plans internally. So, I'd like to have a separate
node being connected to the inner-plan.

Isn't that just a matter of letting the EXPLAIN code print more stuff?
Why can't it?

My GpuHashJoin takes multiple relations to load them a hash-table.
On the other hand, Plan node can have two underlying relations at most
(inner/outer). Outer-side is occupied by the larger relation, so it
needs to make multiple relations visible using inner-branch.
If CustomScan can has a list of multiple underlying plan-nodes, like
Append node, it can represent the structure above in straightforward
way, but I'm uncertain which is the better design.

Right. I think the key point is that it is *possible* to make this
work without a multiexec interface, and it seems like we're agreed
that it is. Now perhaps we will decide that there is enough benefit
in having multiexec support that we want to do it anyway, but it's
clearly not a hard requirement, because it can be done without that in
the way you describe here. Let's leave to the future the decision as
to how to proceed here; getting the basic thing done is hard enough.

--
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

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

On Thu, Sep 11, 2014 at 8:40 PM, Kouhei Kaigai <kaigai@ak.jp.nec.com> wrote:

On Thu, Sep 11, 2014 at 11:24 AM, Kouhei Kaigai
<kaigai@ak.jp.nec.com>
wrote:

Don't the changes to src/backend/optimizer/plan/createplan.c
belong in patch #2?

The borderline between #1 and #2 is little bit bogus. So, I moved
most of portion into #1, however, invocation of InitCustomScan
(that is a callback in CustomPlanMethod) in create_custom_plan() is

still in #2.

Eh, create_custom_scan() certainly looks like it is in #1 from here,
or at least part of it is. It calculates tlist and clauses and then
does nothing with them. That clearly can't be the right division.

I think it would make sense to have create_custom_scan() compute
tlist and clauses first, and then pass those to CreateCustomPlan().
Then you don't need a separate InitCustomScan() - which is misnamed
anyway, since it has nothing to do with ExecInitCustomScan().

The only reason why I put separate hooks here is, create_custom_scan()
needs to know exact size of the CustomScan node (including private
fields), however, it is helpful for extensions to kick its callback to
initialize the node next to the common initialization stuff.

Why does it need to know that? I don't see that it's doing anything that
requires knowing the size of that node, and if it is, I think it shouldn't
be. That should get delegated to the callback provided by the custom plan
provider.

Sorry, my explanation might be confusable. The create_custom_scan() does not
need to know the exact size of the CustomScan (or its inheritance) because of
the two separated hooks; one is node allocation time, the other is the tail
of the series of initialization.
If we have only one hook here, we need to have a mechanism to informs
create_custom_scan() an exact size of the CustomScan node; including private
fields managed by the provider, instead of the first hook on node allocation
time. In this case, node allocation shall be processed by create_custom_scan()
and it has to know exact size of the node to be allocated.

How do I implement the feature here? Is the combination of static node size
and callback on the tail more simple than the existing design that takes two
individual hooks on create_custom_scan()?

Regarding to the naming, how about GetCustomScan() instead of

InitCustomScan()?

It follows the manner in create_foreignscan_plan().

I guess that's a bit better, but come to think of it, I'd really like to
avoid baking in the assumption that the custom path provider has to return
any particular type of plan node. A good start would be to give it a name
that doesn't imply that - e.g. PlanCustomPath().

OK, I'll use this naming.

OK, I revised. Now custom-scan assumes it has a particular valid
relation to be scanned, so no code path with scanrelid == 0 at this

moment.

Let us revisit this scenario when custom-scan replaces relation-joins.
In this case, custom-scan will not be associated with a particular
base- relation, thus it needs to admit a custom-scan node with
scanrelid

== 0.

Yeah, I guess the question there is whether we'll want let CustomScan
have scanrelid == 0 or require that CustomJoin be used there instead.

Right now, I cannot imagine a use case that requires individual
CustomJoin node because CustomScan with scanrelid==0 (that performs
like custom-plan rather than custom-scan in actually) is sufficient.

If a CustomScan gets chosen instead of built-in join logics, it shall
looks like a relation scan on the virtual one that is consists of two
underlying relation. Callbacks of the CustomScan has a responsibility
to join underlying relations; that is invisible from the core executor.

It seems to me CustomScan with scanrelid==0 is sufficient to implement
an alternative logic on relation joins, don't need an individual node
from the standpoint of executor.

That's valid logic, but it's not the only way to do it. If we have CustomScan
and CustomJoin, either of them will require some adaption to handle this
case. We can either allow a custom scan that isn't scanning any particular
relation (i.e. scanrelid == 0), or we can allow a custom join that has no
children. I don't know which way will come out cleaner, and I think it's
good to leave that decision to one side for now.

Yep. I agree with you. It may not be productive discussion to conclude
this design topic right now. Let's assume CustomScan scans on a particular
relation (scanrelid != 0) on the first revision.

Why can't the Custom(GpuHashJoin) node build the hash table
internally instead of using a separate node?

It's possible, however, it prevents to check sub-plans using
EXPLAIN if we manage inner-plans internally. So, I'd like to have a
separate node being connected to the inner-plan.

Isn't that just a matter of letting the EXPLAIN code print more stuff?
Why can't it?

My GpuHashJoin takes multiple relations to load them a hash-table.
On the other hand, Plan node can have two underlying relations at most
(inner/outer). Outer-side is occupied by the larger relation, so it
needs to make multiple relations visible using inner-branch.
If CustomScan can has a list of multiple underlying plan-nodes, like
Append node, it can represent the structure above in straightforward
way, but I'm uncertain which is the better design.

Right. I think the key point is that it is *possible* to make this work
without a multiexec interface, and it seems like we're agreed that it is.
Now perhaps we will decide that there is enough benefit in having multiexec
support that we want to do it anyway, but it's clearly not a hard requirement,
because it can be done without that in the way you describe here. Let's
leave to the future the decision as to how to proceed here; getting the
basic thing done is hard enough.

OK, let's postpone the discussion on the custom-join support.
Either of approaches (1. multi-exec support, or 2. multiple subplans like
Append) is sufficient for this purpose, and the multi-exec interface is
a way to implement it, not a goal.

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

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

On Mon, Sep 15, 2014 at 8:38 AM, Kouhei Kaigai <kaigai@ak.jp.nec.com> wrote:

The only reason why I put separate hooks here is, create_custom_scan()
needs to know exact size of the CustomScan node (including private
fields), however, it is helpful for extensions to kick its callback to
initialize the node next to the common initialization stuff.

Why does it need to know that? I don't see that it's doing anything that
requires knowing the size of that node, and if it is, I think it shouldn't
be. That should get delegated to the callback provided by the custom plan
provider.

Sorry, my explanation might be confusable. The create_custom_scan() does not
need to know the exact size of the CustomScan (or its inheritance) because of
the two separated hooks; one is node allocation time, the other is the tail
of the series of initialization.
If we have only one hook here, we need to have a mechanism to informs
create_custom_scan() an exact size of the CustomScan node; including private
fields managed by the provider, instead of the first hook on node allocation
time. In this case, node allocation shall be processed by create_custom_scan()
and it has to know exact size of the node to be allocated.

How do I implement the feature here? Is the combination of static node size
and callback on the tail more simple than the existing design that takes two
individual hooks on create_custom_scan()?

I still don't get it. Right now, the logic in create_custom_scan(),
which I think should really be create_custom_plan() or
create_plan_from_custom_path(), basically looks like this:

1. call hook function CreateCustomPlan
2. compute values for tlist and clauses
3. pass those values to hook function InitCustomScan()
4. call copy_path_costsize

What I think we should do is:

1. compute values for tlist and clauses
2. pass those values to hook function PlanCustomPath(), which will return a Plan
3. call copy_path_costsize

create_custom_scan() does not need to allocate the node. You don't
need the node to be allocated before computing tlist and clauses.

--
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

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

On Mon, Sep 15, 2014 at 8:38 AM, Kouhei Kaigai <kaigai@ak.jp.nec.com> wrote:

The only reason why I put separate hooks here is,
create_custom_scan() needs to know exact size of the CustomScan
node (including private fields), however, it is helpful for
extensions to kick its callback to initialize the node next to the

common initialization stuff.

Why does it need to know that? I don't see that it's doing anything
that requires knowing the size of that node, and if it is, I think it
shouldn't be. That should get delegated to the callback provided by
the custom plan provider.

Sorry, my explanation might be confusable. The create_custom_scan()
does not need to know the exact size of the CustomScan (or its
inheritance) because of the two separated hooks; one is node
allocation time, the other is the tail of the series of initialization.
If we have only one hook here, we need to have a mechanism to informs
create_custom_scan() an exact size of the CustomScan node; including
private fields managed by the provider, instead of the first hook on
node allocation time. In this case, node allocation shall be processed
by create_custom_scan() and it has to know exact size of the node to be

allocated.

How do I implement the feature here? Is the combination of static node
size and callback on the tail more simple than the existing design
that takes two individual hooks on create_custom_scan()?

I still don't get it. Right now, the logic in create_custom_scan(), which
I think should really be create_custom_plan() or
create_plan_from_custom_path(), basically looks like this:

1. call hook function CreateCustomPlan
2. compute values for tlist and clauses
3. pass those values to hook function InitCustomScan() 4. call
copy_path_costsize

What I think we should do is:

1. compute values for tlist and clauses
2. pass those values to hook function PlanCustomPath(), which will return
a Plan 3. call copy_path_costsize

create_custom_scan() does not need to allocate the node. You don't need
the node to be allocated before computing tlist and clauses.

Thanks, I could get the point.
I'll revise the patch according to the suggestion above.

It seems to me, we can also apply similar manner on ExecInitCustomScan().
The current implementation doing is:
1. call CreateCustomScanState() to allocate a CustomScanState node
2. common initialization of the fields on CustomScanState, but not private
fields.
3. call BeginCustomScan() to initialize remaining stuffs and begin execution.

If BeginCustomScan() is re-defined to accept values for common initialization
portions and to return a CustomScanState node, we may be able to eliminate
the CreateCustomScanState() hook.

Unlike create_custom_scan() case, it takes more number of values for common
initialization portions; expression tree of tlist and quals, scan and result
tuple-slot, projection info and relation handler. It may mess up the interface
specification.
In addition, BeginCustomScan() has to belong to CustomScanMethods, not
CustomexecMethods. I'm uncertain whether it is straightforward location.
(a whisper: It may not need to be separate tables. CustomScan always
populates CustomScanState, unlike relationship between CustomPath and
CustomScan.)

How about your opinion to apply the above manner on ExecInitCustomScan()
also?

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

#36Kouhei Kaigai
kaigai@ak.jp.nec.com
In reply to: Kouhei Kaigai (#35)
3 attachment(s)

Why does it need to know that? I don't see that it's doing
anything that requires knowing the size of that node, and if it is,
I think it shouldn't be. That should get delegated to the callback
provided by the custom plan provider.

Sorry, my explanation might be confusable. The create_custom_scan()
does not need to know the exact size of the CustomScan (or its
inheritance) because of the two separated hooks; one is node
allocation time, the other is the tail of the series of initialization.
If we have only one hook here, we need to have a mechanism to
informs
create_custom_scan() an exact size of the CustomScan node; including
private fields managed by the provider, instead of the first hook on
node allocation time. In this case, node allocation shall be
processed by create_custom_scan() and it has to know exact size of
the node to be

allocated.

How do I implement the feature here? Is the combination of static
node size and callback on the tail more simple than the existing
design that takes two individual hooks on create_custom_scan()?

I still don't get it. Right now, the logic in create_custom_scan(),
which I think should really be create_custom_plan() or
create_plan_from_custom_path(), basically looks like this:

1. call hook function CreateCustomPlan 2. compute values for tlist and
clauses 3. pass those values to hook function InitCustomScan() 4. call
copy_path_costsize

What I think we should do is:

1. compute values for tlist and clauses 2. pass those values to hook
function PlanCustomPath(), which will return a Plan 3. call
copy_path_costsize

create_custom_scan() does not need to allocate the node. You don't
need the node to be allocated before computing tlist and clauses.

Thanks, I could get the point.
I'll revise the patch according to the suggestion above.

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.

It seems to me, we can also apply similar manner on ExecInitCustomScan().
The current implementation doing is:
1. call CreateCustomScanState() to allocate a CustomScanState node 2.
common initialization of the fields on CustomScanState, but not private
fields.
3. call BeginCustomScan() to initialize remaining stuffs and begin
execution.

If BeginCustomScan() is re-defined to accept values for common
initialization portions and to return a CustomScanState node, we may be
able to eliminate the CreateCustomScanState() hook.

Unlike create_custom_scan() case, it takes more number of values for common
initialization portions; expression tree of tlist and quals, scan and result
tuple-slot, projection info and relation handler. It may mess up the
interface specification.
In addition, BeginCustomScan() has to belong to CustomScanMethods, not
CustomexecMethods. I'm uncertain whether it is straightforward location.
(a whisper: It may not need to be separate tables. CustomScan always
populates CustomScanState, unlike relationship between CustomPath and
CustomScan.)

How about your opinion to apply the above manner on ExecInitCustomScan()
also?

I kept existing implementation around ExecInitCustomScan() right now.

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

Attachments:

pgsql-v9.5-custom-scan.part-3.v10.patchapplication/octet-stream; name=pgsql-v9.5-custom-scan.part-3.v10.patchDownload
 contrib/Makefile                       |   1 +
 contrib/ctidscan/Makefile              |  18 +
 contrib/ctidscan/ctidscan.c            | 885 +++++++++++++++++++++++++++++++++
 contrib/ctidscan/ctidscan.control      |   5 +
 contrib/ctidscan/expected/ctidscan.out | 312 ++++++++++++
 contrib/ctidscan/sql/ctidscan.sql      |  54 ++
 src/include/catalog/pg_operator.h      |   3 +
 7 files changed, 1278 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..79cd8db
--- /dev/null
+++ b/contrib/ctidscan/expected/ctidscan.out
@@ -0,0 +1,312 @@
+--
+-- Regression Tests for Custom Plan APIs
+--
+LOAD '$libdir/ctidscan';
+-- 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                              
+----------------------------------------------------------------------
+ Custom (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 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 (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 (ctidscan) on t1
+               Filter: (ctid < '(2,10)'::tid)
+               ctid quals: (ctid < '(2,10)'::tid)
+   ->  Sort
+         Sort Key: t2.ctid
+         ->  Custom (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 (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 (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)
+
+-- 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..9ac6df8
--- /dev/null
+++ b/contrib/ctidscan/sql/ctidscan.sql
@@ -0,0 +1,54 @@
+--
+-- Regression Tests for Custom Plan APIs
+--
+
+LOAD '$libdir/ctidscan';
+
+-- 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;
+
+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);
+
+-- 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.v10.patchapplication/octet-stream; name=pgsql-v9.5-custom-scan.part-2.v10.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    |  33 ++-
 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, 833 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..579479d 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";
+			custom_name = ((CustomScan *) plan)->methods->CustomName;
+			if (custom_name)
+				pname = psprintf("Custom (%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", 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 aa053a0..c31f065 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
@@ -4012,6 +4035,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 29a12bd..f3a9d24 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");
@@ -2862,6 +2874,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 8f73674..ea09c30 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -84,7 +84,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);
@@ -2640,7 +2639,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 4d717df..e230dcd 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,34 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 			}
 			break;
 
+		case T_CustomScan:
+			{
+				CustomScan *cscan = (CustomScan *) plan;
+
+				/*
+				 * In case when custom-scan node actually run on a plain
+				 * relation, we apply usual preprocessing here. If custom-
+				 * scan node does not have something special in its private
+				 * field, nothing to do.
+				 * Elsewhere, core backend cannot know what is the suitable
+				 * set-reference processing, so everything is the role of
+				 * custom-scan provider.
+				 */
+				if (cscan->scan.scanrelid > 0)
+				{
+					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);
+				}
+				if (cscan->methods->SetCustomScanRef)
+					cscan->methods->SetCustomScanRef(root, cscan, rtoffset);
+			}
+			break;
+
 		case T_NestLoop:
 		case T_MergeJoin:
 		case T_HashJoin:
@@ -1060,7 +1087,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 */
@@ -1158,7 +1185,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 6376784..f5e9dd6 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 c62d219..ba141b5 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"
 
 
 /* ----------------------------------------------------------------
@@ -483,12 +485,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 4504250..ace3bef 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.v10.patchapplication/octet-stream; name=pgsql-v9.5-custom-scan.part-1.v10.patchDownload
 src/backend/nodes/outfuncs.c            |  14 +++++
 src/backend/optimizer/path/allpaths.c   |   3 +
 src/backend/optimizer/plan/createplan.c | 100 ++++++++++++++++++++++++++++++++
 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, 224 insertions(+)

diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e686a6c..29a12bd 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");
@@ -3059,6 +3070,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..8f73674 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -77,6 +77,7 @@ 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 Plan *create_custom_plan(PlannerInfo *root, CustomPath *best_path);
 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,
@@ -261,6 +262,9 @@ create_plan_recurse(PlannerInfo *root, Path *best_path)
 			plan = create_unique_plan(root,
 									  (UniquePath *) best_path);
 			break;
+		case T_CustomScan:
+			plan = create_custom_plan(root, (CustomPath *) best_path);
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) best_path->pathtype);
@@ -1072,6 +1076,102 @@ 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 Plan *
+create_custom_plan(PlannerInfo *root, CustomPath *best_path)
+{
+	Plan		   *custom_plan;
+	RelOptInfo	   *rel = best_path->path.parent;
+	List		   *tlist;
+	List		   *clauses;
+
+	/* CustomPath node is assumed to populate CustomScan node */
+	Assert(best_path->path.pathtype == T_CustomScan);
+
+	/*
+	 * Right now, all we can support is CustomScan node which is associated
+	 * with a particular base relation.
+	 */
+	Assert(rel && rel->reloptkind == RELOPT_BASEREL);
+
+	/*
+	 * We prefer to generate a tlist containing all the var-nodes
+	 * in order, for cheaper projection cost, if available.
+	 * use_physical_tlist() makes a centralized decision, then
+	 * build up a tlist from the physical structure of the target
+	 * relation.
+	 */
+	if (use_physical_tlist(root, rel))
+		tlist = build_physical_tlist(root, rel);
+	else
+		tlist = build_path_tlist(root, &best_path->path);
+
+	/*
+	 * Extract the relevant restriction clauses from the parent relation.
+	 * The executor must apply all these restrictions during the scan,
+	 * except for pseudoconstants which we'll take care of below.
+	 */
+	clauses = rel->baserestrictinfo;	
+
+	/*
+     * If this is a parameterized scan, we also need to enforce all
+	 * the join clauses available from the outer relation(s).
+	 */
+	if (best_path->path.param_info)
+		clauses = list_concat(list_copy(clauses),
+							  best_path->path.param_info->ppi_clauses);
+
+	/*
+	 * Sort clauses into the best execution order, although custom-scan
+	 * provider can reorder them again.
+	 */
+	clauses = order_qual_clauses(root, clauses);
+
+	/*
+	 * Replace outer-relation variables with nestloop params.
+	 * Note that any other clauses which is managed by extension
+	 * itself has to be handled in InitCustomPlan() method, as
+	 * built-in code doing.
+	 */
+	if (best_path->path.param_info)
+		clauses = (List *)replace_nestloop_params(root, (Node *)clauses);
+
+	/*
+	 * Create a CustomScan (or its inheritance) node according to
+	 * the supplied CustomPath.
+	 */
+	custom_plan = best_path->methods->PlanCustomPath(root,
+													 rel,
+													 best_path,
+													 tlist,
+													 clauses);
+
+	/* additional sanity checks */
+	Assert(nodeTag(custom_plan) == best_path->path.pathtype);
+
+	if (IsA(custom_plan, CustomScan))
+	{
+		Index	scanrelid = ((CustomScan *)custom_plan)->scan.scanrelid;
+
+		if (scanrelid != rel->relid)
+			elog(ERROR, "Bug? CustomScan tried to scan incorrect relation");
+	}
+	else
+		elog(ERROR, "unexpected node: %d", (int)nodeTag(custom_plan));
+
+	/*
+	 * Copy cost data from Path to Plan; no need to make custom-plan
+	 * providers do this
+	 */
+	copy_path_costsize(custom_plan, &best_path->path);
+
+	return custom_plan;
+}
 
 /*****************************************************************************
  *
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 a031b88..6376784 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 3b9c683..c62d219 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -479,6 +479,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 dacbe9c..e03c4d0 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"
@@ -881,6 +882,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);
#37Robert Haas
robertmhaas@gmail.com
In reply to: Kouhei Kaigai (#36)

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.

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".

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

--
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

#38Merlin Moncure
mmoncure@gmail.com
In reply to: Kouhei Kaigai (#6)

On Tue, Jul 8, 2014 at 6:55 AM, Kouhei Kaigai <kaigai@ak.jp.nec.com> wrote:

* Syntax also reflects what the command does more. New syntax to
define custom plan provider is:
CREATE CUSTOM PLAN PROVIDER <cpp_name>
FOR <cpp_class> HANDLER <cpp_function>;

-1 on 'cpp' prefix. I don't see acronyms used in the syntax
documentation and cpp will make people reflexively think 'c++'. How
about <provider_name> and <provider_function>?

merlin

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

#39Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Merlin Moncure (#38)

2014-10-02 0:41 GMT+09:00 Merlin Moncure <mmoncure@gmail.com>:

On Tue, Jul 8, 2014 at 6:55 AM, Kouhei Kaigai <kaigai@ak.jp.nec.com> wrote:

* Syntax also reflects what the command does more. New syntax to
define custom plan provider is:
CREATE CUSTOM PLAN PROVIDER <cpp_name>
FOR <cpp_class> HANDLER <cpp_function>;

-1 on 'cpp' prefix. I don't see acronyms used in the syntax
documentation and cpp will make people reflexively think 'c++'. How
about <provider_name> and <provider_function>?

It is not a living code. I already eliminated the SQL syntax portion,
instead of the internal interface (register_custom_path_provider)
that registers callbacks on extension load time.

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