 contrib/ctidscan/Makefile                  |   19 +
 contrib/ctidscan/ctidscan--1.0.sql         |   12 +
 contrib/ctidscan/ctidscan.c                | 1695 ++++++++++++++++++++++++++++
 contrib/ctidscan/ctidscan.control          |    5 +
 contrib/ctidscan/expected/ctidscan.out     |  294 +++++
 contrib/ctidscan/sql/ctidscan.sql          |   50 +
 src/backend/catalog/Makefile               |    2 +-
 src/backend/catalog/dependency.c           |   12 +-
 src/backend/catalog/objectaddress.c        |   60 +
 src/backend/commands/Makefile              |    2 +-
 src/backend/commands/custom_plan.c         |  335 ++++++
 src/backend/commands/dropcmds.c            |    5 +
 src/backend/commands/explain.c             |   38 +
 src/backend/executor/Makefile              |    2 +-
 src/backend/executor/execAmi.c             |   24 +
 src/backend/executor/execProcnode.c        |   19 +
 src/backend/executor/nodeCustom.c          |  100 ++
 src/backend/nodes/copyfuncs.c              |   47 +
 src/backend/nodes/equalfuncs.c             |   14 +
 src/backend/nodes/outfuncs.c               |   39 +
 src/backend/optimizer/path/allpaths.c      |   31 +-
 src/backend/optimizer/path/joinpath.c      |    7 +
 src/backend/optimizer/plan/createplan.c    |   30 +
 src/backend/optimizer/plan/setrefs.c       |   14 +-
 src/backend/optimizer/plan/subselect.c     |   21 +-
 src/backend/parser/gram.y                  |   91 +-
 src/backend/tcop/utility.c                 |   16 +
 src/backend/utils/adt/ruleutils.c          |   52 +
 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       |   51 +
 src/include/catalog/pg_operator.h          |    3 +
 src/include/commands/custom_plan.h         |   59 +
 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              |   51 +
 src/include/nodes/relation.h               |   32 +
 src/include/optimizer/subselect.h          |    6 +
 src/include/parser/kwlist.h                |    3 +
 src/include/utils/builtins.h               |    2 +
 src/include/utils/syscache.h               |    2 +
 src/test/regress/expected/sanity_check.out |    1 +
 45 files changed, 3327 insertions(+), 38 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..5aca9b9
--- /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 HANDLER ctidscanaddpath;
diff --git a/contrib/ctidscan/ctidscan.c b/contrib/ctidscan/ctidscan.c
new file mode 100644
index 0000000..5bf7ba6
--- /dev/null
+++ b/contrib/ctidscan/ctidscan.c
@@ -0,0 +1,1695 @@
+/*
+ * 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/custom_plan.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/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;
+	Index			scanrelid;
+	List		   *ctid_quals;
+} CtidScanPlan;
+
+typedef struct {
+	CustomPlanState	cps;
+	Index			scanrelid;		/* range table index of the relation */
+	Relation		scan_rel;		/* relation to be scanned */
+	HeapScanDesc	scan_desc;		/* scan-descriptor of the relation */
+	TupleTableSlot *scan_slot;		/* tuple slot for scanning */
+	List		   *ctid_quals;		/* list of ExprState for inequality ops */
+	ItemPointerData	ip_min;			/* minimum ItemPointer */
+	ItemPointerData	ip_max;			/* maximum ItemPointer */
+	int				ip_min_comp;	/* comparison policy to ip_min */
+	int				ip_max_comp;	/* comparison policy to ip_max */
+	bool			ip_needs_eval;	/* true, if needs to seek again */
+} CtidScanPlanState;
+
+static CustomPathMethods	ctidscan_path_methods;
+static CustomPlanMethods	ctidscan_plan_methods;
+static CustomPlanStateMethods ctidscan_ps_methods;
+
+/* function declarations */
+void	_PG_init(void);
+Datum	CtidScanAddPath(PG_FUNCTION_ARGS);
+
+/*
+ * Functions below are copied from the core PostgreSQL because of
+ * static declaration. Even though it is helpful common part for
+ * custom-plan providers, however, one argument from other side is
+ * it may prevent future improvement if people thought these are
+ * well defined API set.
+ * So, right now, we copied these implementation from the core.
+ */
+static bool use_physical_tlist(PlannerInfo *root, RelOptInfo *rel);
+static List *order_qual_clauses(PlannerInfo *root, List *clauses);
+static Node *replace_nestloop_params(PlannerInfo *root, Node *expr);
+static List *fix_scan_list(PlannerInfo *root, List *node, int rtoffset,
+						   void (*fn_fix_expr_common)(
+							   PlannerInfo *root,
+							   Node *node));
+static bool tlist_matches_tupdesc(PlanState *ps, List *tlist,
+								  Index varno, TupleDesc tupdesc);
+static List *build_path_tlist(PlannerInfo *root, Path *path);
+
+#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,
+				   Plan *(*fn_create_plan)(
+					   PlannerInfo *root,
+					   Path *best_path))
+{
+	CtidScanPath   *ctid_path = (CtidScanPath *) best_path;
+	CtidScanPlan   *ctid_plan;
+	Path		   *path = &ctid_path->cpath.path;
+	RelOptInfo	   *rel = path->parent;
+	List		   *tlist;
+	List		   *scan_clauses;
+
+	Assert(rel->relid > 0);
+
+	/*
+	 * XXX - logic was copied from create_scan_plan
+	 */
+	if (use_physical_tlist(root, rel))
+	{
+		tlist = build_physical_tlist(root, rel);
+		if (tlist == NIL)
+			tlist = build_path_tlist(root, path);
+	}
+	else
+		tlist = build_path_tlist(root, path);
+
+	scan_clauses = rel->baserestrictinfo;
+	if (path->param_info)
+		scan_clauses = list_concat(list_copy(scan_clauses),
+								   path->param_info->ppi_clauses);
+	/* Sort clauses into best execution order */
+	scan_clauses = order_qual_clauses(root, scan_clauses);
+	/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
+	scan_clauses = extract_actual_clauses(scan_clauses, false);
+	/* Replace any outer-relation variables with nestloop params */
+	if (path->param_info)
+	{
+		scan_clauses = (List *)
+			replace_nestloop_params(root, (Node *) scan_clauses);
+	}
+
+	/* OK, set up CtidScanPlan */
+	ctid_plan = palloc0(sizeof(CtidScanPlan));
+	ctid_plan->cplan.plan.type = T_CustomPlan;
+	ctid_plan->cplan.plan.targetlist = tlist;
+	ctid_plan->cplan.plan.qual = scan_clauses;
+	ctid_plan->cplan.methods = &ctidscan_plan_methods;
+	ctid_plan->scanrelid = rel->relid;
+	ctid_plan->ctid_quals = ctid_path->ctid_quals;
+
+	return &ctid_plan->cplan;
+}
+
+/*
+ * TextOutCtidScanPath - A method of CustomPath; that shows a text
+ * representation of the supplied CustomPath object.
+ */
+static void
+TextOutCtidScanPath(StringInfo str, 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
+ */
+static void
+SetCtidScanPlanRef(PlannerInfo *root,
+				   CustomPlan *custom_plan,
+				   int rtoffset,
+				   Plan *(*fn_set_plan_refs)(PlannerInfo *root,
+											 Plan *plan,
+											 int rtoffset),
+				   void (*fn_fix_expr_common)(PlannerInfo *root,
+											  Node *node))
+{
+	CtidScanPlan   *ctid_plan = (CtidScanPlan *) custom_plan;
+
+	ctid_plan->scanrelid += rtoffset;
+	ctid_plan->cplan.plan.targetlist =
+		fix_scan_list(root, ctid_plan->cplan.plan.targetlist, rtoffset,
+					  fn_fix_expr_common);
+	ctid_plan->cplan.plan.qual =
+		fix_scan_list(root, ctid_plan->cplan.plan.qual, rtoffset,
+					  fn_fix_expr_common);
+	ctid_plan->ctid_quals =
+		fix_scan_list(root, ctid_plan->ctid_quals, rtoffset,
+					  fn_fix_expr_common);
+}
+
+/*
+ * 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 subselect.c
+ */
+static Bitmapset *
+FinalizeCtidScanPlan(PlannerInfo *root,
+					 CustomPlan *custom_plan,
+					 Bitmapset *paramids,
+					 Bitmapset *valid_params,
+					 Bitmapset *scan_params,
+					 Bitmapset *(*fn_finalize_plan)(
+						 PlannerInfo *root,
+						 Plan *plan,
+						 Bitmapset *valid_params,
+						 Bitmapset *scan_params),
+					 Bitmapset *(*fn_finalize_primnode)(
+						 Node *node,
+						 struct finalize_primnode_context *context))
+{
+	finalize_primnode_context context;
+	CtidScanPlan   *ctid_plan = (CtidScanPlan *) custom_plan;
+
+	context.root = root;
+	context.paramids = paramids;
+
+	/* applies finalize_primnode() on ctid_quals also */
+	fn_finalize_primnode((Node *)ctid_plan->ctid_quals, &context);
+
+	return bms_add_members(paramids, scan_params);
+}
+
+/*
+ * CreateCtidScanPlanState - A method of CustomPlan; that populate a custom
+ * object being delivered from CustomPlanState type, according to the supplied
+ * CustomPath object.
+ */
+static CustomPlanState *
+CreateCtidScanPlanState(CustomPlan *custom_plan)
+{
+	CtidScanPlanState  *ctps = palloc0(sizeof(CtidScanPlanState));
+
+	ctps->cps.ps.type = T_CustomPlanState;
+	ctps->cps.methods = &ctidscan_ps_methods;
+
+	return &ctps->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, " :scanrelid %u",
+					 ctid_plan->scanrelid);
+	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,
+				 void (*fn_CopyPlanFields)(const Plan *from,
+										   Plan *newnode))
+{
+	CtidScanPlan   *oldnode = (CtidScanPlan *) from;
+	CtidScanPlan   *newnode = palloc0(sizeof(CtidScanPlan));
+
+	newnode->cplan.plan.type = T_CustomPlan;
+
+	fn_CopyPlanFields(&oldnode->cplan.plan, &newnode->cplan.plan);
+
+	newnode->scanrelid = oldnode->scanrelid;
+	newnode->ctid_quals = copyObject(oldnode->ctid_quals);
+
+	return &newnode->cplan;
+}
+
+
+static bool
+CTidEvalScanZone(CtidScanPlanState *ctps)
+{
+	ExprContext	   *econtext = ctps->cps.ps.ps_ExprContext;
+	ListCell	   *lc;
+
+	/*
+	 * See ItemPointerCompare(), ip_max_comp shall be usually either 1 or
+	 * 0 if tid of fetched records are larger than or equal with ip_min.
+	 * To detect end of scan, we shall check whether the result of
+	 * ItemPointerCompare() is less than ip_max_comp, so it never touch
+	 * the point if ip_max_comp is -1, because all the result is either
+	 * 1, 0 or -1. So, it is same as "open ended" as if no termination
+	 * condition was set.
+	 */
+	ctps->ip_min_comp = -1;
+	ctps->ip_max_comp = 1;
+
+	/* Walks on the inequality operators */
+	foreach (lc, ctps->ctid_quals)
+	{
+		FuncExprState  *fexstate = (FuncExprState *) lfirst(lc);
+		OpExpr		   *op = (OpExpr *)fexstate->xprstate.expr;
+		Node		   *arg1 = linitial(op->args);
+		Node		   *arg2 = lsecond(op->args);
+		Oid				opno;
+		ExprState	   *exstate;
+		ItemPointer		itemptr;
+		bool			isnull;
+
+		if (IsCTIDVar(arg1, ctps->scanrelid))
+		{
+			exstate = (ExprState *) lsecond(fexstate->args);
+			opno = op->opno;
+		}
+		else if (IsCTIDVar(arg2, ctps->scanrelid))
+		{
+			exstate = (ExprState *) linitial(fexstate->args);
+			opno = get_commutator(op->opno);
+		}
+		else
+			elog(ERROR, "could not identify CTID variable");
+
+		itemptr = (ItemPointer)
+			DatumGetPointer(ExecEvalExprSwitchContext(exstate,
+													  econtext,
+													  &isnull,
+													  NULL));
+		if (!isnull)
+		{
+			/*
+			 * OK, we could calculate a particular TID that should be
+			 * larger than, less than or equal with fetched record, thus,
+			 * it allows to determine upper or lower bounds of this scan.
+			 */
+			switch (opno)
+			{
+			case TIDLessOperator:
+				if (ctps->ip_max_comp > 0 ||
+					ItemPointerCompare(itemptr, &ctps->ip_max) <= 0)
+				{
+					ItemPointerCopy(itemptr, &ctps->ip_max);
+					ctps->ip_max_comp = -1;
+				}
+				break;
+			case TIDLessEqualOperator:
+				if (ctps->ip_max_comp > 0 ||
+					ItemPointerCompare(itemptr, &ctps->ip_max) < 0)
+				{
+					ItemPointerCopy(itemptr, &ctps->ip_max);
+					ctps->ip_max_comp = 0;
+				}
+				break;
+			case TIDGreaterOperator:
+				if (ctps->ip_min_comp < 0 ||
+					ItemPointerCompare(itemptr, &ctps->ip_min) >= 0)
+				{
+					ItemPointerCopy(itemptr, &ctps->ip_min);
+					ctps->ip_min_comp = 0;
+				}
+				break;
+			case TIDGreaterEqualOperator:
+				if (ctps->ip_min_comp < 0 ||
+					ItemPointerCompare(itemptr, &ctps->ip_min) > 0)
+				{
+					ItemPointerCopy(itemptr, &ctps->ip_min);
+					ctps->ip_min_comp = 1;
+				}
+				break;
+			default:
+				elog(ERROR, "unsupported operator");
+				break;
+			}
+		}
+		else
+		{
+			/*
+			 * 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.
+			 */
+			return false;
+		}
+	}
+	return true;
+}
+
+/*
+ * BeginCtidScan - A method of CustomPlanState; that initializes
+ * the supplied CtidScanPlanState object, at begining of the executor.
+ */
+static void
+BeginCtidScan(CustomPlanState *node, EState *estate, int eflags)
+{
+	CtidScanPlanState *ctps = (CtidScanPlanState *) node;
+	CtidScanPlan   *ctid_plan = (CtidScanPlan *) node->ps.plan;
+	Index			scanrelid = ctid_plan->scanrelid;
+	List		   *tlist;
+	TupleDesc		tupdesc;
+
+	ctps->ctid_quals = (List *)
+		ExecInitExpr((Expr *)ctid_plan->ctid_quals, &node->ps);
+
+	/* Do nothing anymore in EXPLAIN (no ANALYZE) case. */
+	if (eflags & EXEC_FLAG_EXPLAIN_ONLY)
+		return;
+
+	/* open relation and scan descriptor, then assign tuple slot */
+	ctps->scanrelid = scanrelid;
+	ctps->scan_rel = ExecOpenScanRelation(estate, scanrelid, eflags);
+	ctps->scan_desc = heap_beginscan(ctps->scan_rel,
+									 estate->es_snapshot, 0, NULL);
+	ctps->scan_slot = ExecAllocTableSlot(&estate->es_tupleTable);
+	ExecSetSlotDescriptor(ctps->scan_slot,
+						  RelationGetDescr(ctps->scan_rel));
+
+	/*
+	 * Logic copied from ExecAssignScanProjectionInfo - If target-list
+	 * is equivalent to the relation's definition, clear the projection
+	 * info.
+	 */
+	tlist = ctid_plan->cplan.plan.targetlist;
+	tupdesc = ctps->scan_slot->tts_tupleDescriptor;
+	if (tlist_matches_tupdesc(&node->ps, tlist, scanrelid, tupdesc))
+		node->ps.ps_ProjInfo = NULL;
+	else
+		ExecAssignProjectionInfo(&node->ps, tupdesc);
+
+	/* enforce to evaluate item-pointer on the first iteration */
+	ctps->ip_needs_eval = true;
+}
+
+/*
+ * CTidSeekPosition
+ *
+ * It seeks current scan position into a particular point we specified.
+ * Next heap_getnext() will fetch a record from the point we sought.
+ * It returns false, if specified position was out of range thus does not
+ * make sense to scan any mode. Elsewhere, true shall be return.
+ */
+static bool
+CTidSeekPosition(HeapScanDesc scan, ItemPointer pos, ScanDirection direction)
+{
+	BlockNumber		bnum = BlockIdGetBlockNumber(&pos->ip_blkid);
+	ItemPointerData	save_mctid;
+	int				save_mindex;
+
+	Assert(direction == BackwardScanDirection ||
+		   direction == ForwardScanDirection);
+
+	/*
+	 * In case when block-number is out of the range, it is obvious that
+	 * no tuples shall be fetched if forward scan direction. On the other
+	 * hand, we have nothing special for backward scan direction.
+	 * Note that heap_getnext() shall return NULL tuple just after
+	 * heap_rescan() if NoMovementScanDirection is given. Caller of this
+	 * function override scan direction if 'true' was returned, so it makes
+	 * this scan terminated immediately.
+	 */
+	if (bnum >= scan->rs_nblocks)
+	{
+		heap_rescan(scan, NULL);
+		/* Termination of this scan immediately */
+		if (direction == ForwardScanDirection)
+			return true;
+		/* Elsewhere, backward scan from the beginning */
+		return false;
+	}
+
+	/* save the marked position */
+	ItemPointerCopy(&scan->rs_mctid, &save_mctid);
+	save_mindex = scan->rs_mindex;
+
+	/*
+	 * Ensure the block that includes the position shall be loaded on
+	 * heap_restrpos(). Because heap_restrpos() internally calls
+	 * heapgettup() or heapgettup_pagemode() that kicks heapgetpage()
+	 * when rs_cblock is different from the block number being pointed
+	 * by rs_mctid, it makes sense to put invalid block number not to
+	 * match previous value.
+	 */
+	scan->rs_cblock = InvalidBlockNumber;
+
+	/* Put a pseudo value as if heap_markpos() save a position. */
+	ItemPointerCopy(pos, &scan->rs_mctid);
+	if (scan->rs_pageatatime)
+		scan->rs_mindex = ItemPointerGetOffsetNumber(pos) - 1;
+
+	/* Seek to the point */
+	heap_restrpos(scan);
+
+	/* restore the marked position */
+	ItemPointerCopy(&save_mctid, &scan->rs_mctid);
+	scan->rs_mindex = save_mindex;
+
+	return true;
+}
+
+/*
+ * CTidAccessCustomScan
+ *
+ * Access method of 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)
+{
+	CtidScanPlanState *ctps = (CtidScanPlanState *) node;
+	HeapScanDesc	scan = ctps->scan_desc;
+	TupleTableSlot *slot = ctps->scan_slot;
+	EState		   *estate = node->ps.state;
+	ScanDirection	direction = estate->es_direction;
+	HeapTuple		tuple;
+
+	if (ctps->ip_needs_eval)
+	{
+		/* It terminates this scan, if result set shall be obvious empty. */
+		if (!CTidEvalScanZone(ctps))
+			return NULL;
+
+		if (direction == ForwardScanDirection)
+		{
+			/* seek to the point if min-tid was obvious */
+			if (ctps->ip_min_comp != -1)
+			{
+				if (CTidSeekPosition(scan, &ctps->ip_min, direction))
+					direction = NoMovementScanDirection;
+			}
+			else if (scan->rs_inited)
+				heap_rescan(scan, NULL);
+		}
+		else if (direction == BackwardScanDirection)
+		{
+			/* seek to the point if max-tid was obvious */
+			if (ctps->ip_max_comp != 1)
+			{
+				if (CTidSeekPosition(scan, &ctps->ip_max, direction))
+					direction = NoMovementScanDirection;
+			}
+			else if (scan->rs_inited)
+				heap_rescan(scan, NULL);
+		}
+		else
+			elog(ERROR, "unexpected scan direction");
+
+		ctps->ip_needs_eval = false;
+	}
+
+	/*
+	 * get the next tuple from the table
+	 */
+	tuple = heap_getnext(scan, direction);
+	if (!HeapTupleIsValid(tuple))
+		return NULL;
+
+	/*
+	 * check whether the fetched tuple reached to the upper bound
+	 * if forward scan, or the lower bound if backward scan.
+	 */
+	if (direction == ForwardScanDirection)
+	{
+		if (ItemPointerCompare(&tuple->t_self,
+							   &ctps->ip_max) > ctps->ip_max_comp)
+			return NULL;
+	}
+	else if (direction == BackwardScanDirection)
+	{
+		if (ItemPointerCompare(&scan->rs_ctup.t_self,
+							   &ctps->ip_min) < ctps->ip_min_comp)
+			return NULL;
+	}
+	ExecStoreTuple(tuple, slot, scan->rs_cbuf, false);
+
+	return slot;
+}
+
+/*
+ * ExecCtidScan - A method of CustomPlanState; that fetches a tuple
+ * from the relation, if exist anymore.
+ * Most of the logic was copied from ExecScan() because CustomPlanState
+ * is not devlivered from ScanState, so we cannot apply this routine
+ * as is.
+ */
+static TupleTableSlot *
+ExecCtidScan(CustomPlanState *node)
+{
+	ExprContext *econtext;
+	List	   *qual;
+	ProjectionInfo *projInfo;
+	ExprDoneCond isDone;
+	TupleTableSlot *resultSlot;
+
+	/*
+	 * Fetch data from node
+	 */
+	qual = node->ps.qual;
+	projInfo = node->ps.ps_ProjInfo;
+	econtext = node->ps.ps_ExprContext;
+
+	/*
+	 * If we have neither a qual to check nor a projection to do, just skip
+	 * all the overhead and return the raw scan tuple.
+	 */
+	if (!qual && !projInfo)
+	{
+		ResetExprContext(econtext);
+		return CTidAccessCustomScan(node);
+	}
+
+	/*
+	 * Check to see if we're still projecting out tuples from a previous scan
+	 * tuple (because there is a function-returning-set in the projection
+	 * expressions).  If so, try to project another one.
+	 */
+	if (node->ps.ps_TupFromTlist)
+	{
+		Assert(projInfo);		/* can't get here if not projecting */
+		resultSlot = ExecProject(projInfo, &isDone);
+		if (isDone == ExprMultipleResult)
+			return resultSlot;
+		/* Done with that source tuple... */
+		node->ps.ps_TupFromTlist = false;
+	}
+
+	/*
+	 * Reset per-tuple memory context to free any expression evaluation
+	 * storage allocated in the previous tuple cycle.  Note this can't happen
+	 * until we're done projecting out tuples from a scan tuple.
+	 */
+	ResetExprContext(econtext);
+
+	/*
+	 * get a tuple from the access method.	Loop until we obtain a tuple that
+	 * passes the qualification.
+	 */
+	for (;;)
+	{
+		TupleTableSlot *slot;
+
+		CHECK_FOR_INTERRUPTS();
+
+		slot = CTidAccessCustomScan(node);
+
+		/*
+		 * if the slot returned by the accessMtd contains NULL, then it means
+		 * there is nothing more to scan so we just return an empty slot,
+		 * being careful to use the projection result slot so it has correct
+		 * tupleDesc.
+		 */
+		if (TupIsNull(slot))
+		{
+			if (projInfo)
+				return ExecClearTuple(projInfo->pi_slot);
+			else
+				return slot;
+		}
+
+		/*
+		 * place the current tuple into the expr context
+		 */
+		econtext->ecxt_scantuple = slot;
+
+		/*
+		 * check that the current tuple satisfies the qual-clause
+		 *
+		 * check for non-nil qual here to avoid a function call to ExecQual()
+		 * when the qual is nil ... saves only a few cycles, but they add up
+		 * ...
+		 */
+		if (!qual || ExecQual(qual, econtext, false))
+		{
+			/*
+			 * Found a satisfactory scan tuple.
+			 */
+			if (projInfo)
+			{
+				/*
+				 * Form a projection tuple, store it in the result tuple slot
+				 * and return it --- unless we find we can project no tuples
+				 * from this scan tuple, in which case continue scan.
+				 */
+				resultSlot = ExecProject(projInfo, &isDone);
+				if (isDone != ExprEndResult)
+				{
+					node->ps.ps_TupFromTlist = (isDone == ExprMultipleResult);
+					return resultSlot;
+				}
+			}
+			else
+			{
+				/*
+				 * Here, we aren't projecting, so just return scan tuple.
+				 */
+				return slot;
+			}
+		}
+		else
+			InstrCountFiltered1(node, 1);
+
+		/*
+		 * Tuple fails qual, so free per-tuple memory and try again.
+		 */
+		ResetExprContext(econtext);
+	}
+}
+
+/*
+ * MultiExecCtidScan - A method of CustomPlanState; that can return
+ * an opaque datum into upper node, however, ctidscan don't support it.
+ */
+static Node *
+MultiExecCtidScan(CustomPlanState *node)
+{
+	elog(ERROR, "MultiExecProcNode is not supported");
+}
+
+/*
+ * CTidEndCustomScan - A method of CustomPlanState; that closes heap and
+ * scan descriptor, and release other related resources.
+ */
+static void
+EndCtidScan(CustomPlanState *node)
+{
+	CtidScanPlanState *ctps = (CtidScanPlanState *)node;
+
+	if (ctps->scan_slot)
+		ExecClearTuple(ctps->scan_slot);
+	if (ctps->scan_desc)
+		heap_endscan(ctps->scan_desc);
+	if (ctps->scan_rel)
+		ExecCloseScanRelation(ctps->scan_rel);
+}
+
+/*
+ * ReScanCtidScan - A method of CustomPlanState; that rewind the current
+ * seek position.
+ */
+static void
+ReScanCtidScan(CustomPlanState *node)
+{
+	CtidScanPlanState *ctps = (CtidScanPlanState *)node;
+	EState		   *estate = node->ps.state;
+
+	/* logic copied from ExecScanReScan */
+	if (estate->es_epqScanDone != NULL)
+	{
+		Index	scanrelid = ((CtidScanPlan *) node->ps.plan)->scanrelid;
+
+		Assert(scanrelid > 0);
+
+		estate->es_epqScanDone[scanrelid - 1] = false;
+	}
+	ctps->ip_needs_eval = true;
+}
+
+/*
+ * ExplanCtidScanTargetRel - A method of CustomPlanState; that output
+ * relation's name to be scanned.
+ */
+static void
+ExplanCtidScanTargetRel(CustomPlanState *node, ExplainState *es)
+{
+	CtidScanPlanState  *ctps = (CtidScanPlanState *) node;
+	CtidScanPlan	   *ctid_plan = (CtidScanPlan *) ctps->cps.ps.plan;
+	Index				rti = ctid_plan->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)
+{
+	CtidScanPlanState  *ctps = (CtidScanPlanState *) node;
+	CtidScanPlan	   *ctid_plan = (CtidScanPlan *) ctps->cps.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->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,
+					   void (*fn_ExplainPreScanNode)(
+						   PlanState *planstate,
+						   Bitmapset **rels_used))
+{
+	CtidScanPlanState  *ctps = (CtidScanPlanState *) node;
+	CtidScanPlan	   *ctid_plan = (CtidScanPlan *) ctps->cps.ps.plan;
+
+	*rels_used = bms_add_member(*rels_used, ctid_plan->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 != CUSTOM_PLAN_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.SetCustomPlanRef = SetCtidScanPlanRef;
+	ctidscan_plan_methods.SupportCustomBackwardScan = SupportCtidBackwardScan;
+	ctidscan_plan_methods.FinalizeCustomPlan = FinalizeCtidScanPlan;
+	ctidscan_plan_methods.CreateCustomPlanState = CreateCtidScanPlanState;
+	ctidscan_plan_methods.TextOutCustomPlan = TextOutCtidScanPlan;
+	ctidscan_plan_methods.CopyCustomPlan = CopyCtidScanPlan;
+
+	/* setup ctidscan_planstate_methods */
+	ctidscan_ps_methods.CustomName = "ctidscan";
+	ctidscan_ps_methods.BeginCustomPlan = BeginCtidScan;
+	ctidscan_ps_methods.ExecCustomPlan = ExecCtidScan;
+	ctidscan_ps_methods.MultiExecCustomPlan = MultiExecCtidScan;
+	ctidscan_ps_methods.EndCustomPlan = EndCtidScan;
+	ctidscan_ps_methods.ReScanCustomPlan = ReScanCtidScan;
+	ctidscan_ps_methods.MarkPosCustomPlan = NULL;
+	ctidscan_ps_methods.RestrPosCustomPlan = NULL;
+	ctidscan_ps_methods.ExplainCustomPlanTargetRel = ExplanCtidScanTargetRel;
+	ctidscan_ps_methods.ExplainCustomPlan = ExplainCtidScan;
+	ctidscan_ps_methods.ExplainCustomPreScanNode = ExplainCtidPreScanNode;
+	ctidscan_ps_methods.GetSpecialCustomVar = NULL;
+}
+
+/* ----------------------------------------------------------------
+ *
+ * copied logic from the core PostgreSQL
+ *
+ * ----------------------------------------------------------------
+ */
+
+/*
+ * use_physical_tlist
+ *		Decide whether to use a tlist matching relation structure,
+ *		rather than only those Vars actually referenced.
+ */
+static bool
+use_physical_tlist(PlannerInfo *root, RelOptInfo *rel)
+{
+	int			i;
+	ListCell   *lc;
+
+	/*
+	 * We can do this for real relation scans, subquery scans, function scans,
+	 * values scans, and CTE scans (but not for, eg, joins).
+	 */
+	if (rel->rtekind != RTE_RELATION &&
+		rel->rtekind != RTE_SUBQUERY &&
+		rel->rtekind != RTE_FUNCTION &&
+		rel->rtekind != RTE_VALUES &&
+		rel->rtekind != RTE_CTE)
+		return false;
+
+	/*
+	 * Can't do it with inheritance cases either (mainly because Append
+	 * doesn't project).
+	 */
+	if (rel->reloptkind != RELOPT_BASEREL)
+		return false;
+
+	/*
+	 * Can't do it if any system columns or whole-row Vars are requested.
+	 * (This could possibly be fixed but would take some fragile assumptions
+	 * in setrefs.c, I think.)
+	 */
+	for (i = rel->min_attr; i <= 0; i++)
+	{
+		if (!bms_is_empty(rel->attr_needed[i - rel->min_attr]))
+			return false;
+	}
+
+	/*
+	 * Can't do it if the rel is required to emit any placeholder expressions,
+	 * either.
+	 */
+	foreach(lc, root->placeholder_list)
+	{
+		PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc);
+
+		if (bms_nonempty_difference(phinfo->ph_needed, rel->relids) &&
+			bms_is_subset(phinfo->ph_eval_at, rel->relids))
+			return false;
+	}
+
+	return true;
+}
+
+/*
+ * order_qual_clauses
+ *		Given a list of qual clauses that will all be evaluated at the same
+ *		plan node, sort the list into the order we want to check the quals
+ *		in at runtime.
+ *
+ * Ideally the order should be driven by a combination of execution cost and
+ * selectivity, but it's not immediately clear how to account for both,
+ * and given the uncertainty of the estimates the reliability of the decisions
+ * would be doubtful anyway.  So we just order by estimated per-tuple cost,
+ * being careful not to change the order when (as is often the case) the
+ * estimates are identical.
+ *
+ * Although this will work on either bare clauses or RestrictInfos, it's
+ * much faster to apply it to RestrictInfos, since it can re-use cost
+ * information that is cached in RestrictInfos.
+ *
+ * Note: some callers pass lists that contain entries that will later be
+ * removed; this is the easiest way to let this routine see RestrictInfos
+ * instead of bare clauses.  It's OK because we only sort by cost, but
+ * a cost/selectivity combination would likely do the wrong thing.
+ */
+static List *
+order_qual_clauses(PlannerInfo *root, List *clauses)
+{
+	typedef struct
+	{
+		Node	   *clause;
+		Cost		cost;
+	} QualItem;
+	int			nitems = list_length(clauses);
+	QualItem   *items;
+	ListCell   *lc;
+	int			i;
+	List	   *result;
+
+	/* No need to work hard for 0 or 1 clause */
+	if (nitems <= 1)
+		return clauses;
+
+	/*
+	 * Collect the items and costs into an array.  This is to avoid repeated
+	 * cost_qual_eval work if the inputs aren't RestrictInfos.
+	 */
+	items = (QualItem *) palloc(nitems * sizeof(QualItem));
+	i = 0;
+	foreach(lc, clauses)
+	{
+		Node	   *clause = (Node *) lfirst(lc);
+		QualCost	qcost;
+
+		cost_qual_eval_node(&qcost, clause, root);
+		items[i].clause = clause;
+		items[i].cost = qcost.per_tuple;
+		i++;
+	}
+
+	/*
+	 * Sort.  We don't use qsort() because it's not guaranteed stable for
+	 * equal keys.	The expected number of entries is small enough that a
+	 * simple insertion sort should be good enough.
+	 */
+	for (i = 1; i < nitems; i++)
+	{
+		QualItem	newitem = items[i];
+		int			j;
+
+		/* insert newitem into the already-sorted subarray */
+		for (j = i; j > 0; j--)
+		{
+			if (newitem.cost >= items[j - 1].cost)
+				break;
+			items[j] = items[j - 1];
+		}
+		items[j] = newitem;
+	}
+
+	/* Convert back to a list */
+	result = NIL;
+	for (i = 0; i < nitems; i++)
+		result = lappend(result, items[i].clause);
+
+	return result;
+}
+
+/*
+ * replace_nestloop_params
+ *	  Replace outer-relation Vars and PlaceHolderVars in the given expression
+ *	  with nestloop Params
+ *
+ * All Vars and PlaceHolderVars belonging to the relation(s) identified by
+ * root->curOuterRels are replaced by Params, and entries are added to
+ * root->curOuterParams if not already present.
+ */
+static Node *
+replace_nestloop_params_mutator(Node *node, PlannerInfo *root)
+{
+	if (node == NULL)
+		return NULL;
+	if (IsA(node, Var))
+	{
+		Var		   *var = (Var *) node;
+		Param	   *param;
+		NestLoopParam *nlp;
+		ListCell   *lc;
+
+		/* Upper-level Vars should be long gone at this point */
+		Assert(var->varlevelsup == 0);
+		/* If not to be replaced, we can just return the Var unmodified */
+		if (!bms_is_member(var->varno, root->curOuterRels))
+			return node;
+		/* Create a Param representing the Var */
+		param = assign_nestloop_param_var(root, var);
+		/* Is this param already listed in root->curOuterParams? */
+		foreach(lc, root->curOuterParams)
+		{
+			nlp = (NestLoopParam *) lfirst(lc);
+			if (nlp->paramno == param->paramid)
+			{
+				Assert(equal(var, nlp->paramval));
+				/* Present, so we can just return the Param */
+				return (Node *) param;
+			}
+		}
+		/* No, so add it */
+		nlp = makeNode(NestLoopParam);
+		nlp->paramno = param->paramid;
+		nlp->paramval = var;
+		root->curOuterParams = lappend(root->curOuterParams, nlp);
+		/* And return the replacement Param */
+		return (Node *) param;
+	}
+	if (IsA(node, PlaceHolderVar))
+	{
+		PlaceHolderVar *phv = (PlaceHolderVar *) node;
+		Param	   *param;
+		NestLoopParam *nlp;
+		ListCell   *lc;
+
+		/* Upper-level PlaceHolderVars should be long gone at this point */
+		Assert(phv->phlevelsup == 0);
+
+		/*
+		 * Check whether we need to replace the PHV.  We use bms_overlap as a
+		 * cheap/quick test to see if the PHV might be evaluated in the outer
+		 * rels, and then grab its PlaceHolderInfo to tell for sure.
+		 */
+		if (!bms_overlap(phv->phrels, root->curOuterRels) ||
+		  !bms_is_subset(find_placeholder_info(root, phv, false)->ph_eval_at,
+						 root->curOuterRels))
+		{
+			/*
+			 * We can't replace the whole PHV, but we might still need to
+			 * replace Vars or PHVs within its expression, in case it ends up
+			 * actually getting evaluated here.  (It might get evaluated in
+			 * this plan node, or some child node; in the latter case we don't
+			 * really need to process the expression here, but we haven't got
+			 * enough info to tell if that's the case.)  Flat-copy the PHV
+			 * node and then recurse on its expression.
+			 *
+			 * Note that after doing this, we might have different
+			 * representations of the contents of the same PHV in different
+			 * parts of the plan tree.	This is OK because equal() will just
+			 * match on phid/phlevelsup, so setrefs.c will still recognize an
+			 * upper-level reference to a lower-level copy of the same PHV.
+			 */
+			PlaceHolderVar *newphv = makeNode(PlaceHolderVar);
+
+			memcpy(newphv, phv, sizeof(PlaceHolderVar));
+			newphv->phexpr = (Expr *)
+				replace_nestloop_params_mutator((Node *) phv->phexpr,
+												root);
+			return (Node *) newphv;
+		}
+		/* Create a Param representing the PlaceHolderVar */
+		param = assign_nestloop_param_placeholdervar(root, phv);
+		/* Is this param already listed in root->curOuterParams? */
+		foreach(lc, root->curOuterParams)
+		{
+			nlp = (NestLoopParam *) lfirst(lc);
+			if (nlp->paramno == param->paramid)
+			{
+				Assert(equal(phv, nlp->paramval));
+				/* Present, so we can just return the Param */
+				return (Node *) param;
+			}
+		}
+		/* No, so add it */
+		nlp = makeNode(NestLoopParam);
+		nlp->paramno = param->paramid;
+		nlp->paramval = (Var *) phv;
+		root->curOuterParams = lappend(root->curOuterParams, nlp);
+		/* And return the replacement Param */
+		return (Node *) param;
+	}
+	return expression_tree_mutator(node,
+								   replace_nestloop_params_mutator,
+								   (void *) root);
+}
+
+static Node *
+replace_nestloop_params(PlannerInfo *root, Node *expr)
+{
+	/* No setup needed for tree walk, so away we go */
+	return replace_nestloop_params_mutator(expr, root);
+}
+
+/*
+ * fix_scan_expr
+ *		Do set_plan_references processing on a scan-level expression
+ *
+ * This consists of incrementing all Vars' varnos by rtoffset,
+ * looking up operator opcode info for OpExpr and related nodes,
+ * and adding OIDs from regclass Const nodes into root->glob->relationOids.
+ */
+typedef struct
+{
+	PlannerInfo *root;
+	int			rtoffset;
+	void	  (*fn_fix_expr_common)(PlannerInfo *root, Node *node);
+} fix_scan_expr_context;
+
+static Node *
+fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context)
+{
+	if (node == NULL)
+		return NULL;
+	if (IsA(node, Var))
+	{
+		Var		   *var = copyObject(node);
+
+		Assert(var->varlevelsup == 0);
+
+		/*
+		 * We should not see any Vars marked INNER_VAR or OUTER_VAR.  But an
+		 * indexqual expression could contain INDEX_VAR Vars.
+		 */
+		Assert(var->varno != INNER_VAR);
+		Assert(var->varno != OUTER_VAR);
+		if (!IS_SPECIAL_VARNO(var->varno))
+			var->varno += context->rtoffset;
+		if (var->varnoold > 0)
+			var->varnoold += context->rtoffset;
+		return (Node *) var;
+	}
+	if (IsA(node, CurrentOfExpr))
+	{
+		CurrentOfExpr *cexpr = (CurrentOfExpr *) copyObject(node);
+
+		Assert(cexpr->cvarno != INNER_VAR);
+		Assert(cexpr->cvarno != OUTER_VAR);
+		if (!IS_SPECIAL_VARNO(cexpr->cvarno))
+			cexpr->cvarno += context->rtoffset;
+		return (Node *) cexpr;
+	}
+	if (IsA(node, PlaceHolderVar))
+	{
+		/* At scan level, we should always just evaluate the contained expr */
+		PlaceHolderVar *phv = (PlaceHolderVar *) node;
+
+		return fix_scan_expr_mutator((Node *) phv->phexpr, context);
+	}
+	context->fn_fix_expr_common(context->root, node);
+	return expression_tree_mutator(node, fix_scan_expr_mutator,
+								   (void *) context);
+}
+
+static bool
+fix_scan_expr_walker(Node *node, fix_scan_expr_context *context)
+{
+	if (node == NULL)
+		return false;
+	Assert(!IsA(node, PlaceHolderVar));
+	context->fn_fix_expr_common(context->root, node);
+	return expression_tree_walker(node, fix_scan_expr_walker,
+								  (void *) context);
+}
+
+static List *fix_scan_list(PlannerInfo *root, List *expr_list, int rtoffset,
+						   void (*fn_fix_expr_common)(
+							   PlannerInfo *root,
+							   Node *node))
+{
+	fix_scan_expr_context context;
+
+	context.root = root;
+	context.rtoffset = rtoffset;
+	context.fn_fix_expr_common = fn_fix_expr_common;
+
+	if (rtoffset != 0 || root->glob->lastPHId != 0)
+	{
+		return (List *)fix_scan_expr_mutator((Node *)expr_list, &context);
+	}
+	else
+	{
+		/*
+		 * If rtoffset == 0, we don't need to change any Vars, and if there
+		 * are no placeholders anywhere we won't need to remove them.  Then
+		 * it's OK to just scribble on the input node tree instead of copying
+		 * (since the only change, filling in any unset opfuncid fields, is
+		 * harmless).  This saves just enough cycles to be noticeable on
+		 * trivial queries.
+		 */
+		(void) fix_scan_expr_walker((Node *)expr_list, &context);
+		return expr_list;
+	}
+}
+
+static bool
+tlist_matches_tupdesc(PlanState *ps, List *tlist, Index varno,
+					  TupleDesc tupdesc)
+{
+	int			numattrs = tupdesc->natts;
+	int			attrno;
+	bool		hasoid;
+	ListCell   *tlist_item = list_head(tlist);
+
+	/* Check the tlist attributes */
+	for (attrno = 1; attrno <= numattrs; attrno++)
+	{
+		Form_pg_attribute att_tup = tupdesc->attrs[attrno - 1];
+		Var		   *var;
+
+		if (tlist_item == NULL)
+			return false;		/* tlist too short */
+		var = (Var *) ((TargetEntry *) lfirst(tlist_item))->expr;
+		if (!var || !IsA(var, Var))
+			return false;		/* tlist item not a Var */
+		/* if these Asserts fail, planner messed up */
+		Assert(var->varno == varno);
+		Assert(var->varlevelsup == 0);
+		if (var->varattno != attrno)
+			return false;		/* out of order */
+		if (att_tup->attisdropped)
+			return false;		/* table contains dropped columns */
+
+		/*
+		 * Note: usually the Var's type should match the tupdesc exactly, but
+		 * in situations involving unions of columns that have different
+		 * typmods, the Var may have come from above the union and hence have
+		 * typmod -1.  This is a legitimate situation since the Var still
+		 * describes the column, just not as exactly as the tupdesc does. We
+		 * could change the planner to prevent it, but it'd then insert
+		 * projection steps just to convert from specific typmod to typmod -1,
+		 * which is pretty silly.
+		 */
+		if (var->vartype != att_tup->atttypid ||
+			(var->vartypmod != att_tup->atttypmod &&
+			 var->vartypmod != -1))
+			return false;		/* type mismatch */
+
+		tlist_item = lnext(tlist_item);
+	}
+
+	if (tlist_item)
+		return false;			/* tlist too long */
+
+	/*
+	 * If the plan context requires a particular hasoid setting, then that has
+	 * to match, too.
+	 */
+	if (ExecContextForcesOids(ps, &hasoid) &&
+		hasoid != tupdesc->tdhasoid)
+		return false;
+
+	return true;
+}
+
+/*
+ * Build a target list (ie, a list of TargetEntry) for the Path's output.
+ */
+static List *
+build_path_tlist(PlannerInfo *root, Path *path)
+{
+	RelOptInfo *rel = path->parent;
+	List	   *tlist = NIL;
+	int			resno = 1;
+	ListCell   *v;
+
+	foreach(v, rel->reltargetlist)
+	{
+		/* Do we really need to copy here?	Not sure */
+		Node	   *node = (Node *) copyObject(lfirst(v));
+
+		/*
+		 * If it's a parameterized path, there might be lateral references in
+		 * the tlist, which need to be replaced with Params.  There's no need
+		 * to remake the TargetEntry nodes, so apply this to each list item
+		 * separately.
+		 */
+		if (path->param_info)
+			node = replace_nestloop_params(root, node);
+
+		tlist = lappend(tlist, makeTargetEntry((Expr *) node,
+											   resno,
+											   NULL,
+											   false));
+		resno++;
+	}
+	return tlist;
+}
diff --git a/contrib/ctidscan/ctidscan.control b/contrib/ctidscan/ctidscan.control
new file mode 100644
index 0000000..123635e
--- /dev/null
+++ b/contrib/ctidscan/ctidscan.control
@@ -0,0 +1,5 @@
+# ctidscan extension
+comment = 'ctidscan for custom-plan demonstration'
+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/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 e511669..5dc318e 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"
@@ -54,6 +55,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/pg_user_mapping.h"
 #include "commands/comment.h"
+#include "commands/custom_plan.h"
 #include "commands/defrem.h"
 #include "commands/event_trigger.h"
 #include "commands/extension.h"
@@ -154,7 +156,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 +1252,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 +2323,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 2b837a9..219fb5f 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"
@@ -51,6 +52,7 @@
 #include "catalog/pg_ts_template.h"
 #include "catalog/pg_type.h"
 #include "catalog/pg_user_mapping.h"
+#include "commands/custom_plan.h"
 #include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/event_trigger.h"
@@ -152,6 +154,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 +543,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 +770,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 +833,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 +1318,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 +2190,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 +2616,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 +3373,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..a96a0f8
--- /dev/null
+++ b/src/backend/commands/custom_plan.c
@@ -0,0 +1,335 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/custom_plan.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"
+
+/* static variables */
+static List *custom_scan_callchain = NIL;
+static List *custom_join_callchain = NIL;
+static bool	callchain_is_ready = false;
+static MemoryContext custom_memcxt = NULL;
+
+static void
+invalidateCustomPlanCallchain(Datum arg, int cacheid, uint32 hashvalue)
+{
+	MemoryContextReset(custom_memcxt);
+	callchain_is_ready = false;
+	custom_scan_callchain = NIL;
+	custom_join_callchain = NIL;
+}
+
+static void
+setupCustomPlanCallchain(void)
+{
+	Relation		rel;
+	SysScanDesc		scan;
+	HeapTuple		tuple;
+	MemoryContext	oldcxt;
+
+	custom_scan_callchain = NIL;
+	custom_join_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_memcxt);
+	while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+	{
+		Form_pg_custom_plan	custForm
+			= (Form_pg_custom_plan) GETSTRUCT(tuple);
+
+		if ((custForm->custclass & CUSTOM_PLAN_CLASS_SCAN) != 0)
+		{
+			custom_scan_callchain = lappend_oid(custom_scan_callchain,
+												custForm->custhandler);
+		}
+		if ((custForm->custclass & CUSTOM_PLAN_CLASS_JOIN) != 0)
+		{
+			custom_join_callchain = lappend_oid(custom_join_callchain,
+												custForm->custhandler);
+		}
+	}
+	MemoryContextSwitchTo(oldcxt);
+	systable_endscan(scan);
+
+	heap_close(rel, AccessShareLock);
+
+	callchain_is_ready = true;
+}
+
+static void
+initCustomPlanCallchain(void)
+{
+	/* memory context to keep callchain for custom-plans */
+	custom_memcxt = AllocSetContextCreate(CacheMemoryContext,
+										  "custom plan memory context",
+										  ALLOCSET_DEFAULT_MINSIZE,
+										  ALLOCSET_DEFAULT_INITSIZE,
+										  ALLOCSET_DEFAULT_MAXSIZE);
+
+	/* flush cached callchain on catalog updates */
+	CacheRegisterSyscacheCallback(CUSTOMPLANOID,
+								  invalidateCustomPlanCallchain,
+								  (Datum) 0);
+	/* also, initial setting up */
+	setupCustomPlanCallchain();
+}
+
+/*
+ * CallCustomScanProviders
+ *
+ * A callchain on relation scan. custom-plan provider can add alternative
+ * scan paths delivered from CustomPath class.
+ */
+void
+CallCustomScanProviders(PlannerInfo *root,
+						RelOptInfo *baserel,
+						RangeTblEntry *rte)
+{
+	customScanArg	sarg;
+	ListCell	   *cell;
+
+	if (!custom_memcxt)
+		initCustomPlanCallchain();
+	else if (!callchain_is_ready)
+		setupCustomPlanCallchain();
+
+	Assert(callchain_is_ready);
+	sarg.custom_class = CUSTOM_PLAN_CLASS_SCAN;
+	sarg.root = root;
+	sarg.baserel = baserel;
+	sarg.rte = rte;
+
+	foreach (cell, custom_scan_callchain)
+	{
+		(void) OidFunctionCall1(lfirst_oid(cell),
+								PointerGetDatum(&sarg));
+	}
+}
+
+/*
+ * CallCustomJoinProviders
+ *
+ * A callchain on relation scan. custom-plan provider can add alternative
+ * join paths delivered from CustomPath class.
+ */
+void
+CallCustomJoinProviders(PlannerInfo *root,
+						RelOptInfo *joinrel,
+						RelOptInfo *outerrel,
+						RelOptInfo *innerrel,
+						JoinType jointype,
+						SpecialJoinInfo *sjinfo,
+						List *restrictlist,
+						Relids param_source_rels,
+						Relids extra_lateral_rels)
+{
+	customJoinArg	jarg;
+	ListCell	   *cell;
+
+	if (!custom_memcxt)
+		initCustomPlanCallchain();
+	else if (!callchain_is_ready)
+		setupCustomPlanCallchain();
+
+	Assert(callchain_is_ready);
+	jarg.custom_class = CUSTOM_PLAN_CLASS_JOIN;
+	jarg.root = root;
+	jarg.joinrel = joinrel;
+	jarg.outerrel = outerrel;
+	jarg.innerrel = innerrel;
+	jarg.jointype = jointype;
+	jarg.sjinfo = sjinfo;
+	jarg.restrictlist = restrictlist;
+	jarg.param_source_rels = param_source_rels;
+	jarg.extra_lateral_rels = extra_lateral_rels;
+
+	foreach (cell, custom_join_callchain)
+	{
+		(void) OidFunctionCall1(lfirst_oid(cell),
+								PointerGetDatum(&jarg));
+	}
+}
+
+/*
+ * 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_handler = 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)));
+
+	/* validate custom-plan classes */
+	if (stmt->custom_class != CUSTOM_PLAN_CLASS_SCAN &&
+		stmt->custom_class != CUSTOM_PLAN_CLASS_JOIN &&
+		stmt->custom_class != CUSTOM_PLAN_CLASS_ANY)
+		elog(ERROR, "unexpected custom-plan class: %08x", stmt->custom_class);
+
+	/* parse custom-plan options */
+	foreach (cell, stmt->custom_options)
+	{
+		DefElem	   *defel = lfirst(cell);
+
+		Assert(IsA(defel, DefElem));
+
+		if (strcmp(defel->defname, "handler") == 0)
+		{
+			Oid		argtypes[1];
+
+			if (OidIsValid(cust_handler))
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options")));
+
+			argtypes[0] = INTERNALOID;
+			cust_handler = LookupFuncName((List *)defel->arg,
+										  1, argtypes, false);
+			if (get_func_rettype(cust_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 option: %s",
+				 defel->defname);
+	}
+
+	if (!OidIsValid(cust_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_custname - 1]
+		= DirectFunctionCall1(namein, CStringGetDatum(stmt->custom_name));
+	values[Anum_pg_custom_plan_custclass - 1]
+		= stmt->custom_class;
+	values[Anum_pg_custom_plan_custhandler - 1]
+		= ObjectIdGetDatum(cust_handler);
+
+	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_handler;
+	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/explain.c b/src/backend/commands/explain.c
index 1104cc3..bd1f433 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -721,6 +721,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;
+
+				cps->methods->ExplainCustomPreScanNode(cps, rels_used,
+													   ExplainPreScanNode);
+			}
+			break;
 		case T_ModifyTable:
 			/* cf ExplainModifyTarget */
 			*rels_used = bms_add_member(*rels_used,
@@ -847,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;
 
@@ -935,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;
@@ -1036,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))
@@ -1083,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;
@@ -1347,6 +1374,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..4dece5a 100644
--- a/src/backend/executor/Makefile
+++ b/src/backend/executor/Makefile
@@ -15,7 +15,7 @@ include $(top_builddir)/src/Makefile.global
 OBJS = execAmi.o execCurrent.o execGrouping.o execJunk.o execMain.o \
        execProcnode.o execQual.o execScan.o execTuples.o \
        execUtils.o functions.o instrument.o nodeAppend.o nodeAgg.o \
-       nodeBitmapAnd.o nodeBitmapOr.o \
+       nodeBitmapAnd.o nodeBitmapOr.o nodeCustom.o \
        nodeBitmapHeapscan.o nodeBitmapIndexscan.o nodeHash.o \
        nodeHashjoin.o nodeIndexscan.o nodeIndexonlyscan.o \
        nodeLimit.o nodeLockRows.o \
diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c
index 8c01a63..4c0b5fb 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,16 @@ ExecSupportsBackwardScan(Plan *node)
 			return ExecSupportsBackwardScan(((SubqueryScan *) node)->subplan) &&
 				TargetListSupportsBackwardScan(node->targetlist);
 
+		case T_CustomPlan:
+		case T_CustomPlanMarkPos:
+			{
+				CustomPlan *cplan = (CustomPlan *) node;
+
+				if (cplan->methods->SupportCustomBackwardScan)
+					return cplan->methods->SupportCustomBackwardScan(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 c5ecd18..23a245f 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..1f4356b
--- /dev/null
+++ b/src/backend/executor/nodeCustom.c
@@ -0,0 +1,100 @@
+/* ------------------------------------------------------------------------
+ *
+ * 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->ps.plan = &cplan->plan;
+	cps->ps.state = estate;
+
+	/* create expression context for node */
+	ExecAssignExprContext(estate, &cps->ps);
+	cps->ps.ps_TupFromTlist = false;
+
+	/* initialize child expressions */
+	cps->ps.targetlist = (List *)
+		ExecInitExpr((Expr *) cplan->plan.targetlist,
+					 (PlanState *) cps);
+	cps->ps.qual = (List *)
+		ExecInitExpr((Expr *) cplan->plan.qual,
+					 (PlanState *) cps);
+
+	/* initialization of result tuple slot  */
+	ExecInitResultTupleSlot(estate, &cps->ps);
+	ExecAssignResultTypeFromTL(&cps->ps);
+	cps->ps.ps_ProjInfo = NULL;
+
+	/* then, all the remaining initialization on extension side */
+	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)
+{
+	Assert(cpstate->methods->MultiExecCustomPlan != NULL);
+	return cpstate->methods->MultiExecCustomPlan(cpstate);
+}
+
+void
+ExecEndCustomPlan(CustomPlanState *cpstate)
+{
+	Assert(cpstate->methods->EndCustomPlan != NULL);
+	cpstate->methods->EndCustomPlan(cpstate);
+
+	/* Free the exprcontext */
+	ExecFreeExprContext(&cpstate->ps);
+
+	/* Clean out the tuple table */
+	ExecClearTuple(cpstate->ps.ps_ResultTupleSlot);
+}
+
+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 98ad910..4de93c3 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -598,6 +598,32 @@ _copyForeignScan(const ForeignScan *from)
 }
 
 /*
+ * _copyCustomPlan
+ */
+static CustomPlan *
+_copyCustomPlan(const CustomPlan *from)
+{
+	CustomPlan *newnode =
+		from->methods->CopyCustomPlan(from, CopyPlanFields);
+
+	Assert(IsA(newnode, CustomPlan));
+	return newnode;
+}
+
+/*
+ * _copyCustomPlanMarkPos
+ */
+static CustomPlanMarkPos *
+_copyCustomPlanMarkPos(const CustomPlanMarkPos *from)
+{
+	CustomPlanMarkPos *newnode =
+		from->methods->CopyCustomPlan(from, CopyPlanFields);
+
+	Assert(IsA(newnode, CustomPlanMarkPos));
+	return newnode;
+}
+
+/*
  * CopyJoinFields
  *
  *		This function copies the fields of the Join node.  It is used by
@@ -3821,6 +3847,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 +4022,12 @@ copyObject(const void *from)
 		case T_ForeignScan:
 			retval = _copyForeignScan(from);
 			break;
+		case T_CustomPlan:
+			retval = _copyCustomPlan(from);
+			break;
+		case T_CustomPlanMarkPos:
+			retval = _copyCustomPlanMarkPos(from);
+			break;
 		case T_Join:
 			retval = _copyJoin(from);
 			break;
@@ -4530,6 +4574,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 9901d23..1fdaa64 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 10e8139..602e82a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -564,6 +564,26 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
 }
 
 static void
+_outCustomPlan(StringInfo str, const CustomPlan *node)
+{
+	WRITE_NODE_TYPE("CUSTOMPLAN");
+	_outPlanInfo(str, (const Plan *) node);
+	appendStringInfo(str, " :methods");
+	_outToken(str, node->methods->CustomName);
+	node->methods->TextOutCustomPlan(str, node);
+}
+
+static void
+_outCustomPlanMarkPos(StringInfo str, const CustomPlanMarkPos *node)
+{
+	WRITE_NODE_TYPE("CUSTOMPLANMARKPOS");
+	_outPlanInfo(str, (const Plan *) 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 +1601,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 *)node);
+}
+
+static void
 _outAppendPath(StringInfo str, const AppendPath *node)
 {
 	WRITE_NODE_TYPE("APPENDPATH");
@@ -2829,6 +2859,12 @@ _outNode(StringInfo str, const void *obj)
 			case T_ForeignScan:
 				_outForeignScan(str, obj);
 				break;
+			case T_CustomPlan:
+				_outCustomPlan(str, obj);
+				break;
+			case T_CustomPlanMarkPos:
+				_outCustomPlanMarkPos(str, obj);
+				break;
 			case T_Join:
 				_outJoin(str, obj);
 				break;
@@ -3037,6 +3073,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 5777cb2..53ff647 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -19,6 +19,7 @@
 
 #include "catalog/pg_class.h"
 #include "catalog/pg_operator.h"
+#include "commands/custom_plan.h"
 #include "foreign/fdwapi.h"
 #include "nodes/nodeFuncs.h"
 #ifdef OPTIMIZER_DEBUG
@@ -323,7 +324,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 */
@@ -334,12 +335,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 */
+		CallCustomScanProviders(root, rel, rte);
+
+		/* Select cheapest path */
+		set_cheapest(rel);
 	}
 
 #ifdef OPTIMIZER_DEBUG
@@ -388,9 +394,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);
 }
 
 /*
@@ -416,9 +419,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);
 }
 
 /*
@@ -1247,9 +1247,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);
 }
 
 /*
@@ -1318,9 +1315,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);
 }
 
 /*
@@ -1341,9 +1335,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);
 }
 
 /*
@@ -1410,9 +1401,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);
 }
 
 /*
@@ -1463,9 +1451,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/joinpath.c b/src/backend/optimizer/path/joinpath.c
index a996116..4620fe6 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -259,6 +259,13 @@ add_paths_to_joinrel(PlannerInfo *root,
 							 restrictlist, jointype,
 							 sjinfo, &semifactors,
 							 param_source_rels, extra_lateral_rels);
+
+	/*
+	 * 5. Also consider paths being provided by custom-plan providers
+	 */
+	CallCustomJoinProviders(root, joinrel, outerrel, innerrel,
+							jointype, sjinfo, restrictlist,
+							param_source_rels, extra_lateral_rels);
 }
 
 /*
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 784805f..259d4bd 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -77,6 +77,8 @@ 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 CustomPlan *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 +263,10 @@ create_plan_recurse(PlannerInfo *root, Path *best_path)
 			plan = create_unique_plan(root,
 									  (UniquePath *) best_path);
 			break;
+		case T_CustomPlan:
+			plan = (Plan *) create_custom_plan(root,
+											   (CustomPath *) best_path);
+			break;
 		default:
 			elog(ERROR, "unrecognized node type: %d",
 				 (int) best_path->pathtype);
@@ -1072,6 +1078,30 @@ 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 CustomPlan *
+create_custom_plan(PlannerInfo *root, CustomPath *best_path)
+{
+	CustomPlan	   *cplan;
+
+	/*
+	 * Create a custom-plan object delivered from CustomPlan type,
+	 * according to the supplied CustomPath
+	 */
+	Assert(best_path->methods->CreateCustomPlan != NULL);
+	cplan = best_path->methods->CreateCustomPlan(root, best_path,
+												 create_plan_recurse);
+	Assert(IsA(cplan, CustomPlan) || IsA(cplan, CustomPlanMarkPos));
+
+	/* Copy cost data from Path to Plan; no need to make callback do this */
+	copy_path_costsize(&cplan->plan, &best_path->path);
+
+	return cplan;
+}
 
 /*****************************************************************************
  *
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 46affe7..5d1ad54 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -94,6 +94,7 @@ static Plan *set_subqueryscan_references(PlannerInfo *root,
 							SubqueryScan *plan,
 							int rtoffset);
 static bool trivial_subqueryscan(SubqueryScan *plan);
+static void fix_expr_common(PlannerInfo *root, Node *node);
 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);
@@ -575,7 +576,18 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 					fix_scan_list(root, splan->fdw_exprs, rtoffset);
 			}
 			break;
-
+		case T_CustomPlan:
+			{
+				CustomPlan *cplan = (CustomPlan *) plan;
+
+				Assert(cplan->methods->SetCustomPlanRef != NULL);
+				cplan->methods->SetCustomPlanRef(root,
+												 cplan,
+												 rtoffset,
+												 set_plan_refs,
+												 fix_expr_common);
+			}
+			break;
 		case T_NestLoop:
 		case T_MergeJoin:
 		case T_HashJoin:
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index a3f3583..2a1c119 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -46,12 +46,6 @@ typedef struct process_sublinks_context
 	bool		isTopQual;
 } process_sublinks_context;
 
-typedef struct finalize_primnode_context
-{
-	PlannerInfo *root;
-	Bitmapset  *paramids;		/* Non-local PARAM_EXEC paramids found */
-} finalize_primnode_context;
-
 
 static Node *build_subplan(PlannerInfo *root, Plan *plan, PlannerInfo *subroot,
 			  List *plan_params,
@@ -2236,6 +2230,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_plan,
+														 finalize_primnode);
+			}
+			break;
+
 		case T_ModifyTable:
 			{
 				ModifyTable *mtplan = (ModifyTable *) plan;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7b9895d..f45111c 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 <node>	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,7 +580,7 @@ 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
+	PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLAN PLANS POSITION
 	PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROGRAM
 
@@ -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_P 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
@@ -8652,6 +8660,78 @@ AlterTSConfigurationStmt:
 				}
 		;
 
+/****************************************************************************
+ *
+ *		QUERY:
+ *				CREATE CUSTOM PLAN name FOR <class> 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_P				{ $$ = CUSTOM_PLAN_CLASS_SCAN; }
+			| JOIN				{ $$ = CUSTOM_PLAN_CLASS_JOIN; }
+			| ANY				{ $$ = CUSTOM_PLAN_CLASS_ANY; }
+		;
+
+custom_option:
+			HANDLER handler_name
+			{
+				$$ = makeDefElem("handler", (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;
+			}
+		;
 
 /*****************************************************************************
  *
@@ -12842,6 +12922,7 @@ unreserved_keyword:
 			| CSV
 			| CURRENT_P
 			| CURSOR
+			| CUSTOM
 			| CYCLE
 			| DATA_P
 			| DATABASE
@@ -12954,6 +13035,7 @@ unreserved_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PLAN
 			| PLANS
 			| PRECEDING
 			| PREPARE
@@ -12989,6 +13071,7 @@ unreserved_keyword:
 			| ROWS
 			| RULE
 			| SAVEPOINT
+			| SCAN_P
 			| SCHEMA
 			| SCROLL
 			| SEARCH
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 1846570..3ab01d0 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 36d9953..6891109 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -5385,6 +5385,22 @@ get_utility_query_def(Query *query, deparse_context *context)
 	}
 }
 
+/*
+ * GetSpecialCustomVar
+ *
+ * Utility routine to call optional GetSpecialCustomVar method of
+ * CustomPlanState
+ */
+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 +5432,7 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
 	deparse_columns *colinfo;
 	char	   *refname;
 	char	   *attname;
+	Node	   *expr;
 
 	/* Find appropriate nesting depth */
 	netlevelsup = var->varlevelsup + levelsup;
@@ -5438,6 +5455,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;
@@ -5726,6 +5758,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 79df5b6..be2388e 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 8948589..0962df6 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..01b52bb
--- /dev/null
+++ b/src/include/catalog/pg_custom_plan.h
@@ -0,0 +1,51 @@
+/* -------------------------------------------------------------------------
+ *
+ * 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 */
+	int32		custclass;		/* mask of CUSTOM_CLASS_*; see below */
+	Oid			custhandler;	/* OID of handler function to be called */
+} 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_custhandler	3
+
+/* Bits for custclass */
+#define CUSTOM_PLAN_CLASS_SCAN			(1 << 0)
+#define CUSTOM_PLAN_CLASS_JOIN			(1 << 1)
+#define CUSTOM_PLAN_CLASS_ANY			~0
+
+#endif   /* PG_CUSTOM_PLAN_H */
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index f280af4..63986ab 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/custom_plan.h b/src/include/commands/custom_plan.h
new file mode 100644
index 0000000..155d710
--- /dev/null
+++ b/src/include/commands/custom_plan.h
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------
+ *
+ * custom_plan.h
+ *		Declarations for custom-plan handling
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/commands/custom_plan.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef CUSTOM_PLAN_H
+#define CUSTOM_PLAN_H
+#include "nodes/parsenodes.h"
+#include "optimizer/planmain.h"
+
+/* argument of handler function for CUSTOM_PLAN_CLASS_SCAN */
+typedef struct {
+	uint32			custom_class;
+	PlannerInfo	   *root;
+	RelOptInfo	   *baserel;
+	RangeTblEntry  *rte;
+} customScanArg;
+
+/* argument of handler function for CUSTOM_PLAN_CLASS_JOIN */
+typedef struct {
+	uint32			custom_class;
+	PlannerInfo	   *root;
+	RelOptInfo	   *joinrel;
+	RelOptInfo	   *outerrel;
+	RelOptInfo	   *innerrel;
+	JoinType		jointype;
+	SpecialJoinInfo *sjinfo;
+	List		   *restrictlist;
+	Relids			param_source_rels;
+	Relids			extra_lateral_rels;
+} customJoinArg;
+
+extern void CallCustomScanProviders(PlannerInfo *root,
+									RelOptInfo *baserel,
+									RangeTblEntry *rte);
+
+extern void CallCustomJoinProviders(PlannerInfo *root,
+									RelOptInfo *joinrel,
+									RelOptInfo *outerrel,
+									RelOptInfo *innerrel,
+									JoinType jointype,
+									SpecialJoinInfo *sjinfo,
+									List *restrictlist,
+									Relids param_source_rels,
+									Relids extra_lateral_rels);
+
+extern Oid	get_custom_plan_oid(const char *custom_name, bool missing_ok);
+
+extern void	RemoveCustomPlanById(Oid cust_oid);
+extern Oid	DefineCustomPlan(CreateCustomPlanStmt *stmt);
+
+#endif	/* CUSTOM_PLAN_H */
diff --git a/src/include/executor/nodeCustom.h b/src/include/executor/nodeCustom.h
new file mode 100644
index 0000000..f97917c
--- /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 6c94e8a..97198b6 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 CustomPlanStateMethods;
+struct ExplainState;	/* to avoid to include explain.h here */
+
+typedef struct CustomPlanState
+{
+	PlanState	ps;
+	const struct CustomPlanStateMethods *methods;
+} CustomPlanState;
+
+typedef struct CustomPlanStateMethods
+{
+	const char	   *CustomName;
+	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,
+										void (*fn_ExplainPreScanNode)(
+											PlanState *planstate,
+											Bitmapset **rels_used));
+	Node   *(*GetSpecialCustomVar)(CustomPlanState *node, Var *varnode);
+} CustomPlanStateMethods;
+
 /* ----------------------------------------------------------------
  *				 Join State Information
  * ----------------------------------------------------------------
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 5b8df59..c1c381d 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 18d4991..29b63ab 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;
+	int			custom_class;
+	List	   *custom_options;
+} 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 38c039c..2c8b079 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,55 @@ 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 finalize_primnode_context;	/* to avoid to include subselect.h */
+
+typedef struct CustomPlan
+{
+	Plan		plan;
+	const struct CustomPlanMethods *methods;
+} CustomPlan;
+
+/* almost same to CustomPlan, but support MarkPos/RestorePos */
+typedef CustomPlan CustomPlanMarkPos;
+
+typedef struct CustomPlanMethods
+{
+	const char *CustomName;
+	void	(*SetCustomPlanRef)(PlannerInfo *root,
+								CustomPlan *custom_plan,
+								int rtoffset,
+								Plan *(*fn_set_plan_refs)(PlannerInfo *root,
+														  Plan *plan,
+														  int rtoffset),
+								void (*fn_fix_expr_common)(PlannerInfo *root,
+														   Node *node));
+	bool	(*SupportCustomBackwardScan)(CustomPlan *custom_plan);
+	Bitmapset  *(*FinalizeCustomPlan)(PlannerInfo *root,
+									  CustomPlan *custom_plan,
+									  Bitmapset *paramids,
+									  Bitmapset *valid_params,
+									  Bitmapset *scan_params,
+									  Bitmapset *(*fn_finalize_plan)(
+										  PlannerInfo *root,
+										  Plan *plan,
+										  Bitmapset *valid_params,
+										  Bitmapset *scan_params),
+									  Bitmapset *(*fn_finalize_primnode)(
+										  Node *node,
+										  struct finalize_primnode_context *context));
+	struct CustomPlanState *(*CreateCustomPlanState)(CustomPlan *custom_plan);
+	void	(*TextOutCustomPlan)(StringInfo str,
+								 const CustomPlan *node);
+	CustomPlan *(*CopyCustomPlan)(const CustomPlan *from,
+								  void (*fn_CopyPlanFields)(const Plan *from,
+															Plan *newnode));
+} CustomPlanMethods;
 
 /*
  * ==========
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index c607b36..96ef3a2 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,37 @@ 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,
+							struct Plan *(*fn_create_plan)(
+								PlannerInfo *root,
+								Path *best_path));
+	void (*TextOutCustomPath)(StringInfo str, CustomPath *node);
+} CustomPathMethods;
+
+/*
  * AppendPath represents an Append plan, ie, successive execution of
  * several member plans.
  *
diff --git a/src/include/optimizer/subselect.h b/src/include/optimizer/subselect.h
index 5607e98..a8ad7c5 100644
--- a/src/include/optimizer/subselect.h
+++ b/src/include/optimizer/subselect.h
@@ -15,6 +15,12 @@
 #include "nodes/plannodes.h"
 #include "nodes/relation.h"
 
+typedef struct finalize_primnode_context
+{
+	PlannerInfo	   *root;
+	Bitmapset	   *paramids;	/* Non-local PARAM_EXEC paramids found */
+} finalize_primnode_context;
+
 extern void SS_process_ctes(PlannerInfo *root);
 extern JoinExpr *convert_ANY_sublink_to_join(PlannerInfo *root,
 							SubLink *sublink,
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 61fae22..a769d04 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)
@@ -326,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_P, 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/builtins.h b/src/include/utils/builtins.h
index 33b6dca..abc55b2 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -563,6 +563,8 @@ extern Datum language_handler_in(PG_FUNCTION_ARGS);
 extern Datum language_handler_out(PG_FUNCTION_ARGS);
 extern Datum fdw_handler_in(PG_FUNCTION_ARGS);
 extern Datum fdw_handler_out(PG_FUNCTION_ARGS);
+extern Datum custom_plan_handler_in(PG_FUNCTION_ARGS);
+extern Datum custom_plan_handler_out(PG_FUNCTION_ARGS);
 extern Datum internal_in(PG_FUNCTION_ARGS);
 extern Datum internal_out(PG_FUNCTION_ARGS);
 extern Datum opaque_in(PG_FUNCTION_ARGS);
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
