contrib/Makefile | 1 +
contrib/ctidscan/Makefile | 19 +
contrib/ctidscan/ctidscan--1.0.sql | 12 +
contrib/ctidscan/ctidscan--unpackaged-1.0.sql | 0
contrib/ctidscan/ctidscan.c | 944 ++++++++++++++++++++++
contrib/ctidscan/ctidscan.control | 5 +
contrib/ctidscan/expected/ctidscan.out | 312 +++++++
contrib/ctidscan/sql/ctidscan.sql | 54 ++
doc/src/sgml/catalogs.sgml | 59 ++
doc/src/sgml/custom-plan-provider.sgml | 413 ++++++++++
doc/src/sgml/filelist.sgml | 1 +
doc/src/sgml/postgres.sgml | 1 +
doc/src/sgml/ref/allfiles.sgml | 2 +
doc/src/sgml/ref/create_custom_plan_provider.sgml | 139 ++++
doc/src/sgml/ref/drop_custom_plan_provider.sgml | 108 +++
doc/src/sgml/reference.sgml | 2 +
src/backend/catalog/Makefile | 2 +-
src/backend/catalog/dependency.c | 11 +-
src/backend/catalog/objectaddress.c | 63 ++
src/backend/commands/Makefile | 2 +-
src/backend/commands/custom_plan.c | 188 +++++
src/backend/commands/dropcmds.c | 5 +
src/backend/commands/event_trigger.c | 2 +
src/backend/commands/explain.c | 39 +
src/backend/executor/Makefile | 2 +-
src/backend/executor/execAmi.c | 38 +-
src/backend/executor/execProcnode.c | 18 +
src/backend/executor/nodeCustom.c | 147 ++++
src/backend/nodes/copyfuncs.c | 35 +
src/backend/nodes/equalfuncs.c | 14 +
src/backend/nodes/outfuncs.c | 27 +
src/backend/optimizer/path/allpaths.c | 30 +-
src/backend/optimizer/path/costsize.c | 2 +-
src/backend/optimizer/plan/createplan.c | 97 ++-
src/backend/optimizer/plan/setrefs.c | 11 +-
src/backend/optimizer/plan/subselect.c | 24 +
src/backend/optimizer/util/pathnode.c | 110 +++
src/backend/parser/gram.y | 94 ++-
src/backend/tcop/utility.c | 20 +
src/backend/utils/adt/ruleutils.c | 73 ++
src/backend/utils/cache/syscache.c | 23 +
src/include/catalog/dependency.h | 1 +
src/include/catalog/indexing.h | 6 +
src/include/catalog/pg_custom_plan_provider.h | 56 ++
src/include/catalog/pg_operator.h | 3 +
src/include/commands/defrem.h | 5 +
src/include/executor/executor.h | 2 +-
src/include/executor/nodeCustom.h | 30 +
src/include/nodes/execnodes.h | 44 +
src/include/nodes/nodes.h | 4 +
src/include/nodes/parsenodes.h | 13 +
src/include/nodes/plannodes.h | 37 +
src/include/nodes/relation.h | 27 +
src/include/optimizer/pathnode.h | 14 +
src/include/optimizer/planmain.h | 2 +
src/include/parser/kwlist.h | 4 +
src/include/utils/syscache.h | 2 +
src/test/regress/expected/sanity_check.out | 1 +
58 files changed, 3360 insertions(+), 40 deletions(-)
diff --git a/contrib/Makefile b/contrib/Makefile
index b37d0dd..9b4b6ad 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -12,6 +12,7 @@ SUBDIRS = \
btree_gist \
chkpass \
citext \
+ ctidscan \
cube \
dblink \
dict_int \
diff --git a/contrib/ctidscan/Makefile b/contrib/ctidscan/Makefile
new file mode 100644
index 0000000..1e476a6
--- /dev/null
+++ b/contrib/ctidscan/Makefile
@@ -0,0 +1,19 @@
+# contrib/ctidscan/Makefile
+
+MODULES = ctidscan
+
+EXTENSION = ctidscan
+DATA = ctidscan--1.0.sql
+
+REGRESS = ctidscan
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/ctidscan
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/ctidscan/ctidscan--1.0.sql b/contrib/ctidscan/ctidscan--1.0.sql
new file mode 100644
index 0000000..46420a0
--- /dev/null
+++ b/contrib/ctidscan/ctidscan--1.0.sql
@@ -0,0 +1,12 @@
+--
+-- Create ctidscan handler function
+--
+CREATE FUNCTION ctidscanaddpath(internal)
+ RETURNS pg_catalog.void
+ AS 'MODULE_PATHNAME','CtidScanAddPath'
+ LANGUAGE C STRICT;
+
+--
+-- Create a custom-plan provider
+--
+CREATE CUSTOM PLAN PROVIDER ctidscan FOR scan HANDLER ctidscanaddpath;
diff --git a/contrib/ctidscan/ctidscan--unpackaged-1.0.sql b/contrib/ctidscan/ctidscan--unpackaged-1.0.sql
new file mode 100644
index 0000000..e69de29
diff --git a/contrib/ctidscan/ctidscan.c b/contrib/ctidscan/ctidscan.c
new file mode 100644
index 0000000..5a54fea
--- /dev/null
+++ b/contrib/ctidscan/ctidscan.c
@@ -0,0 +1,944 @@
+/*
+ * ctidscan.c
+ *
+ * Definition of Custom TidScan implementation.
+ *
+ * It is designed to demonstrate Custom Scan APIs; that allows to override
+ * a part of executor node. This extension focus on a workload that tries
+ * to fetch records with tid larger or less than a particular value.
+ * In case when inequality operators were given, this module construct
+ * a custom scan path that enables to skip records not to be read. Then,
+ * if it was the cheapest one, it shall be used to run the query.
+ * Custom Scan APIs callbacks this extension when executor tries to fetch
+ * underlying records, then it utilizes existing heap_getnext() but seek
+ * the records to be read prior to fetching the first record.
+ *
+ * Portions Copyright (c) 2014, PostgreSQL Global Development Group
+ */
+#include "postgres.h"
+#include "access/relscan.h"
+#include "access/sysattr.h"
+#include "catalog/pg_custom_plan_provider.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_type.h"
+#include "commands/defrem.h"
+#include "commands/explain.h"
+#include "executor/executor.h"
+#include "executor/nodeCustom.h"
+#include "fmgr.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "optimizer/cost.h"
+#include "optimizer/paths.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/plancat.h"
+#include "optimizer/planmain.h"
+#include "optimizer/placeholder.h"
+#include "optimizer/restrictinfo.h"
+#include "optimizer/subselect.h"
+#include "parser/parsetree.h"
+#include "storage/bufmgr.h"
+#include "storage/itemptr.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/spccache.h"
+
+PG_MODULE_MAGIC;
+
+typedef struct {
+ CustomPath cpath;
+ List *ctid_quals;
+} CtidScanPath;
+
+typedef struct {
+ CustomPlan cplan;
+ List *ctid_quals;
+} CtidScanPlan;
+
+typedef struct {
+ CustomPlanState cps;
+ List *ctid_quals; /* list of ExprState for inequality ops */
+} CtidScanState;
+
+static CustomPathMethods ctidscan_path_methods;
+static CustomPlanMethods ctidscan_plan_methods;
+static CustomExecMethods ctidscan_exec_methods;
+
+/* function declarations */
+void _PG_init(void);
+Datum CtidScanAddPath(PG_FUNCTION_ARGS);
+
+#define IsCTIDVar(node,rtindex) \
+ ((node) != NULL && \
+ IsA((node), Var) && \
+ ((Var *) (node))->varno == (rtindex) && \
+ ((Var *) (node))->varattno == SelfItemPointerAttributeNumber && \
+ ((Var *) (node))->varlevelsup == 0)
+
+/*
+ * CTidQualFromExpr
+ *
+ * It checks whether the given restriction clauses enables to determine
+ * the zone to be scanned, or not. If one or more restriction clauses are
+ * available, it returns a list of them, or NIL elsewhere.
+ * The caller can consider all the conditions are chained with AND-
+ * boolean operator, so all the operator works for narrowing down the
+ * scope of custom tid scan.
+ */
+static List *
+CTidQualFromExpr(Node *expr, int varno)
+{
+ if (is_opclause(expr))
+ {
+ OpExpr *op = (OpExpr *) expr;
+ Node *arg1;
+ Node *arg2;
+ Node *other = NULL;
+
+ /* only inequality operators are candidate */
+ if (op->opno != TIDLessOperator &&
+ op->opno != TIDLessEqualOperator &&
+ op->opno != TIDGreaterOperator &&
+ op->opno != TIDGreaterEqualOperator)
+ return NULL;
+
+ if (list_length(op->args) != 2)
+ return false;
+
+ arg1 = linitial(op->args);
+ arg2 = lsecond(op->args);
+
+ if (IsCTIDVar(arg1, varno))
+ other = arg2;
+ else if (IsCTIDVar(arg2, varno))
+ other = arg1;
+ else
+ return NULL;
+ if (exprType(other) != TIDOID)
+ return NULL; /* probably can't happen */
+ /* The other argument must be a pseudoconstant */
+ if (!is_pseudo_constant_clause(other))
+ return NULL;
+
+ return list_make1(copyObject(op));
+ }
+ else if (and_clause(expr))
+ {
+ List *rlst = NIL;
+ ListCell *lc;
+
+ foreach(lc, ((BoolExpr *) expr)->args)
+ {
+ List *temp = CTidQualFromExpr((Node *) lfirst(lc), varno);
+
+ rlst = list_concat(rlst, temp);
+ }
+ return rlst;
+ }
+ return NIL;
+}
+
+/*
+ * CTidEstimateCosts
+ *
+ * It estimates cost to scan the target relation according to the given
+ * restriction clauses. Its logic to scan relations are almost same as
+ * SeqScan doing, because it uses regular heap_getnext(), except for
+ * the number of tuples to be scanned if restriction clauses work well.
+*/
+static void
+CTidEstimateCosts(PlannerInfo *root,
+ RelOptInfo *baserel,
+ CtidScanPath *ctid_path)
+{
+ Path *path = &ctid_path->cpath.path;
+ List *ctid_quals = ctid_path->ctid_quals;
+ ListCell *lc;
+ double ntuples;
+ ItemPointerData ip_min;
+ ItemPointerData ip_max;
+ bool has_min_val = false;
+ bool has_max_val = false;
+ BlockNumber num_pages;
+ Cost startup_cost = 0;
+ Cost run_cost = 0;
+ Cost cpu_per_tuple;
+ QualCost qpqual_cost;
+ QualCost ctid_qual_cost;
+ double spc_random_page_cost;
+
+ /* Should only be applied to base relations */
+ Assert(baserel->relid > 0);
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ /* Mark the path with the correct row estimate */
+ if (path->param_info)
+ path->rows = path->param_info->ppi_rows;
+ else
+ path->rows = baserel->rows;
+
+ /* Estimate how many tuples we may retrieve */
+ ItemPointerSet(&ip_min, 0, 0);
+ ItemPointerSet(&ip_max, MaxBlockNumber, MaxOffsetNumber);
+ foreach (lc, ctid_quals)
+ {
+ OpExpr *op = lfirst(lc);
+ Oid opno;
+ Node *other;
+
+ Assert(is_opclause(op));
+ if (IsCTIDVar(linitial(op->args), baserel->relid))
+ {
+ opno = op->opno;
+ other = lsecond(op->args);
+ }
+ else if (IsCTIDVar(lsecond(op->args), baserel->relid))
+ {
+ /* To simplifies, we assume as if Var node is 1st argument */
+ opno = get_commutator(op->opno);
+ other = linitial(op->args);
+ }
+ else
+ elog(ERROR, "could not identify CTID variable");
+
+ if (IsA(other, Const))
+ {
+ ItemPointer ip = (ItemPointer)(((Const *) other)->constvalue);
+
+ /*
+ * Just an rough estimation, we don't distinct inequality and
+ * inequality-or-equal operator.
+ */
+ switch (opno)
+ {
+ case TIDLessOperator:
+ case TIDLessEqualOperator:
+ if (ItemPointerCompare(ip, &ip_max) < 0)
+ ItemPointerCopy(ip, &ip_max);
+ has_max_val = true;
+ break;
+ case TIDGreaterOperator:
+ case TIDGreaterEqualOperator:
+ if (ItemPointerCompare(ip, &ip_min) > 0)
+ ItemPointerCopy(ip, &ip_min);
+ has_min_val = true;
+ break;
+ default:
+ elog(ERROR, "unexpected operator code: %u", op->opno);
+ break;
+ }
+ }
+ }
+
+ /* estimated number of tuples in this relation */
+ ntuples = baserel->pages * baserel->tuples;
+
+ if (has_min_val && has_max_val)
+ {
+ /* case of both side being bounded */
+ BlockNumber bnum_max = BlockIdGetBlockNumber(&ip_max.ip_blkid);
+ BlockNumber bnum_min = BlockIdGetBlockNumber(&ip_min.ip_blkid);
+
+ bnum_max = Min(bnum_max, baserel->pages);
+ bnum_min = Max(bnum_min, 0);
+ num_pages = Min(bnum_max - bnum_min + 1, 1);
+ }
+ else if (has_min_val)
+ {
+ /* case of only lower side being bounded */
+ BlockNumber bnum_max = baserel->pages;
+ BlockNumber bnum_min = BlockIdGetBlockNumber(&ip_min.ip_blkid);
+
+ bnum_min = Max(bnum_min, 0);
+ num_pages = Min(bnum_max - bnum_min + 1, 1);
+ }
+ else if (has_max_val)
+ {
+ /* case of only upper side being bounded */
+ BlockNumber bnum_max = BlockIdGetBlockNumber(&ip_max.ip_blkid);
+ BlockNumber bnum_min = 0;
+
+ bnum_max = Min(bnum_max, baserel->pages);
+ num_pages = Min(bnum_max - bnum_min + 1, 1);
+ }
+ else
+ {
+ /*
+ * Just a rough estimation. We assume half of records shall be
+ * read using this restriction clause, but indeterministic until
+ * executor run it actually.
+ */
+ num_pages = Max((baserel->pages + 1) / 2, 1);
+ }
+ ntuples *= ((double) num_pages) / ((double) baserel->pages);
+
+ /*
+ * The TID qual expressions will be computed once, any other baserestrict
+ * quals once per retrieved tuple.
+ */
+ cost_qual_eval(&ctid_qual_cost, ctid_quals, root);
+
+ /* fetch estimated page cost for tablespace containing table */
+ get_tablespace_page_costs(baserel->reltablespace,
+ &spc_random_page_cost,
+ NULL);
+
+ /* disk costs --- assume each tuple on a different page */
+ run_cost += spc_random_page_cost * ntuples;
+
+ /*
+ * Add scanning CPU costs
+ * (logic copied from get_restriction_qual_cost)
+ */
+ if (path->param_info)
+ {
+ /* Include costs of pushed-down clauses */
+ cost_qual_eval(&qpqual_cost, path->param_info->ppi_clauses, root);
+
+ qpqual_cost.startup += baserel->baserestrictcost.startup;
+ qpqual_cost.per_tuple += baserel->baserestrictcost.per_tuple;
+ }
+ else
+ qpqual_cost = baserel->baserestrictcost;
+
+ /*
+ * We don't decrease cost for the inequality operators, because
+ * it is subset of qpquals and still in.
+ */
+ startup_cost += qpqual_cost.startup + ctid_qual_cost.per_tuple;
+ cpu_per_tuple = cpu_tuple_cost + qpqual_cost.per_tuple -
+ ctid_qual_cost.per_tuple;
+ run_cost = cpu_per_tuple * ntuples;
+
+ path->startup_cost = startup_cost;
+ path->total_cost = startup_cost + run_cost;
+}
+
+/*
+ * CreateCtidScanPlan - A method of CustomPath; that populate a custom
+ * object being delivered from CustomPlan type, according to the supplied
+ * CustomPath object.
+ */
+static Node *
+CreateCtidScanPlan(PlannerInfo *root, CustomPath *best_path)
+{
+ CtidScanPath *ctid_path = (CtidScanPath *) best_path;
+ CtidScanPlan *ctid_scan;
+
+ ctid_scan = palloc0(sizeof(CtidScanPlan));
+ NodeSetTag(ctid_scan, T_CustomPlan);
+ ctid_scan->cplan.methods = &ctidscan_plan_methods;
+ ctid_scan->cplan.flags = ctid_path->cpath.flags;
+ ctid_scan->ctid_quals = ctid_path->ctid_quals;
+
+ return (Node *)&ctid_scan->cplan;
+}
+
+/*
+ * InitCtidScanPlan
+ *
+ * main portion to initialize CustomPlan node.
+ */
+static Plan *
+InitCtidScanPlan(CustomPlan *custom_plan,
+ PlannerInfo *root, CustomPath *best_path,
+ List *tlist, List *clauses)
+{
+ CtidScanPlan *ctid_scan = (CtidScanPlan *)custom_plan;
+ List *ctid_quals = ((CtidScanPath *)best_path)->ctid_quals;
+
+ Assert(ctid_scan->cplan.scan.scanrelid > 0);
+
+ /* Set targetlist as is */
+ ctid_scan->cplan.scan.plan.targetlist = tlist;
+ /* Reduce RestrictInfo list to bare expressions */
+ ctid_scan->cplan.scan.plan.qual
+ = extract_actual_clauses(clauses, false);
+ /* Set ctid related quals */
+ if (best_path->path.param_info)
+ ctid_quals = (List *)replace_nestloop_params(root, (Node *)ctid_quals);
+ ctid_scan->ctid_quals = ctid_quals;
+
+ /*
+ * If there are any pseudoconstant clauses attached to this node, insert a
+ * gating Result node that evaluates the pseudoconstants as one-time
+ * quals.
+ */
+ if (root->hasPseudoConstantQuals)
+ {
+ List *pseudoconstants = extract_actual_clauses(clauses, true);
+
+ if (pseudoconstants != NIL)
+ return (Plan *) make_result(root,
+ tlist,
+ (Node *) pseudoconstants,
+ (Plan *) ctid_scan);
+ }
+ return &ctid_scan->cplan.scan.plan;
+}
+
+/*
+ * TextOutCtidScanPath - A method of CustomPath; that shows a text
+ * representation of the supplied CustomPath object.
+ */
+static void
+TextOutCtidScanPath(StringInfo str, const CustomPath *cpath)
+{
+ CtidScanPath *ctid_path = (CtidScanPath *)cpath;
+
+ appendStringInfo(str, " :ctid_quals %s",
+ nodeToString(ctid_path->ctid_quals));
+}
+
+/*
+ * SetCtidScanPlanRef - A method of CustomPlan; that fixes up rtindex
+ * of Var nodes
+ */
+#define fix_scan_list(root, lst, rtoffset) \
+ ((List *) fix_scan_expr(root, (Node *) (lst), rtoffset))
+
+static void
+SetCtidScanPlanRef(PlannerInfo *root,
+ CustomPlan *custom_plan,
+ int rtoffset)
+{
+ CtidScanPlan *ctidscan = (CtidScanPlan *) custom_plan;
+ Scan *scan = &ctidscan->cplan.scan;
+
+ scan->scanrelid += rtoffset;
+ scan->plan.targetlist =
+ fix_scan_list(root, scan->plan.targetlist, rtoffset);
+ scan->plan.qual =
+ fix_scan_list(root, scan->plan.qual, rtoffset);
+ ctidscan->ctid_quals =
+ fix_scan_list(root, ctidscan->ctid_quals, rtoffset);
+}
+
+/*
+ * FinalizeCtidScanPlan - A method of CustomPlan; that handles callbacks
+ * by finalize_plan().
+ */
+static void
+FinalizeCtidScanPlan(PlannerInfo *root,
+ CustomPlan *custom_plan,
+ bool (*finalize_primnode)(),
+ void *finalize_context)
+{
+ CtidScanPlan *ctid_plan = (CtidScanPlan *) custom_plan;
+
+ /* applies finalize_primnode() on ctid_quals also */
+ finalize_primnode((Node *)ctid_plan->ctid_quals, finalize_context);
+}
+
+/*
+ * CreateCtidScanState - A method of CustomPlan; that populate a custom
+ * object being delivered from CustomPlanState type, according to the
+ * supplied CustomPath object.
+ */
+static Node *
+CreateCtidScanState(CustomPlan *custom_plan)
+{
+ CtidScanState *ctss = palloc0(sizeof(CtidScanState));
+
+ NodeSetTag(ctss, T_CustomPlanState);
+ ctss->cps.methods = &ctidscan_exec_methods;
+ ctss->cps.flags = custom_plan->flags;
+
+ return (Node *)&ctss->cps;
+}
+
+/*
+ * TextOutCtidScanPlan - A method of CustomPlan; that generates text
+ * representation of the given object.
+ */
+static void
+TextOutCtidScanPlan(StringInfo str, const CustomPlan *node)
+{
+ CtidScanPlan *ctid_plan = (CtidScanPlan *) node;
+
+ appendStringInfo(str, " :ctid_quals %s",
+ nodeToString(ctid_plan->ctid_quals));
+}
+
+/*
+ * CopyCtidScanPlan - A method of CustomPlan; that create a copied object.
+ */
+static CustomPlan *
+CopyCtidScanPlan(const CustomPlan *from)
+{
+ CtidScanPlan *oldnode = (CtidScanPlan *) from;
+ CtidScanPlan *newnode = palloc0(sizeof(CtidScanPlan));
+
+ NodeSetTag(newnode, T_CustomPlan);
+ newnode->cplan.methods = oldnode->cplan.methods;
+ newnode->ctid_quals = copyObject(oldnode->ctid_quals);
+
+ return &newnode->cplan;
+}
+
+/*
+ * BeginCtidScan - A method of CustomPlanState; that initializes
+ * the supplied CtidScanState object, at beginning of the executor.
+ */
+static void
+BeginCtidScan(CustomPlanState *node, EState *estate, int eflags)
+{
+ CtidScanState *ctss = (CtidScanState *) node;
+ CtidScanPlan *ctid_plan = (CtidScanPlan *) node->ss.ps.plan;
+
+ /*
+ * In case of custom-plan provider that offers an alternative way
+ * to scan a particular relation, most of the needed initialization,
+ * like relation open or assignment of scan tuple-slot or projection
+ * info, shall be done by the core implementation. So, all we need
+ * to have is initialization of own local properties.
+ */
+ ctss->ctid_quals = (List *)
+ ExecInitExpr((Expr *)ctid_plan->ctid_quals, &node->ss.ps);
+
+ /* Do nothing anymore in EXPLAIN (no ANALYZE) case. */
+ if (eflags & EXEC_FLAG_EXPLAIN_ONLY)
+ return;
+
+ /* scandesc shall be set later */
+ ctss->cps.ss.ss_currentScanDesc = NULL;
+}
+
+/*
+ * ReScanCtidScan - A method of CustomPlanState; that rewind the current
+ * seek position.
+ */
+static void
+ReScanCtidScan(CustomPlanState *node)
+{
+ CtidScanState *ctss = (CtidScanState *)node;
+ HeapScanDesc scan = ctss->cps.ss.ss_currentScanDesc;
+ EState *estate = node->ss.ps.state;
+ ScanDirection direction = estate->es_direction;
+ Relation relation = ctss->cps.ss.ss_currentRelation;
+ ExprContext *econtext = ctss->cps.ss.ps.ps_ExprContext;
+ ScanKeyData keys[2];
+ bool has_ubound = false;
+ bool has_lbound = false;
+ ItemPointerData ip_max;
+ ItemPointerData ip_min;
+ ListCell *lc;
+
+ /* once close the existing scandesc, if any */
+ if (scan)
+ {
+ heap_endscan(scan);
+ scan = ctss->cps.ss.ss_currentScanDesc = NULL;
+ }
+
+ /* walks on the inequality operators */
+ foreach (lc, ctss->ctid_quals)
+ {
+ FuncExprState *fexstate = (FuncExprState *) lfirst(lc);
+ OpExpr *op = (OpExpr *)fexstate->xprstate.expr;
+ Node *arg1 = linitial(op->args);
+ Node *arg2 = lsecond(op->args);
+ Index scanrelid;
+ Oid opno;
+ ExprState *exstate;
+ ItemPointer itemptr;
+ bool isnull;
+
+ scanrelid = ((Scan *)ctss->cps.ss.ps.plan)->scanrelid;
+ if (IsCTIDVar(arg1, scanrelid))
+ {
+ exstate = (ExprState *) lsecond(fexstate->args);
+ opno = op->opno;
+ }
+ else if (IsCTIDVar(arg2, scanrelid))
+ {
+ exstate = (ExprState *) linitial(fexstate->args);
+ opno = get_commutator(op->opno);
+ }
+ else
+ elog(ERROR, "could not identify CTID variable");
+
+ itemptr = (ItemPointer)
+ DatumGetPointer(ExecEvalExprSwitchContext(exstate,
+ econtext,
+ &isnull,
+ NULL));
+ if (isnull)
+ {
+ /*
+ * Whole of the restriction clauses chained with AND- boolean
+ * operators because false, if one of the clauses has NULL result.
+ * So, we can immediately break the evaluation to inform caller
+ * it does not make sense to scan any more.
+ * In this case, scandesc is kept to NULL.
+ */
+ return;
+ }
+
+ switch (opno)
+ {
+ case TIDLessOperator:
+ if (!has_ubound ||
+ ItemPointerCompare(itemptr, &ip_max) <= 0)
+ {
+ ScanKeyInit(&keys[0],
+ SelfItemPointerAttributeNumber,
+ BTLessStrategyNumber,
+ F_TIDLT,
+ PointerGetDatum(itemptr));
+ ItemPointerCopy(itemptr, &ip_max);
+ has_ubound = true;
+ }
+ break;
+
+ case TIDLessEqualOperator:
+ if (!has_ubound ||
+ ItemPointerCompare(itemptr, &ip_max) < 0)
+ {
+ ScanKeyInit(&keys[0],
+ SelfItemPointerAttributeNumber,
+ BTLessEqualStrategyNumber,
+ F_TIDLE,
+ PointerGetDatum(itemptr));
+ ItemPointerCopy(itemptr, &ip_max);
+ has_ubound = true;
+ }
+ break;
+
+ case TIDGreaterOperator:
+ if (!has_lbound ||
+ ItemPointerCompare(itemptr, &ip_min) >= 0)
+ {
+ ScanKeyInit(&keys[1],
+ SelfItemPointerAttributeNumber,
+ BTGreaterStrategyNumber,
+ F_TIDGT,
+ PointerGetDatum(itemptr));
+ ItemPointerCopy(itemptr, &ip_min);
+ has_lbound = true;
+ }
+ break;
+
+ case TIDGreaterEqualOperator:
+ if (!has_lbound ||
+ ItemPointerCompare(itemptr, &ip_min) > 0)
+ {
+ ScanKeyInit(&keys[1],
+ SelfItemPointerAttributeNumber,
+ BTGreaterEqualStrategyNumber,
+ F_TIDGE,
+ PointerGetDatum(itemptr));
+ ItemPointerCopy(itemptr, &ip_min);
+ has_lbound = true;
+ }
+ break;
+
+ default:
+ elog(ERROR, "unsupported operator");
+ break;
+ }
+ }
+
+ /* begin heapscan with the key above */
+ if (has_ubound && has_lbound)
+ scan = heap_beginscan(relation, estate->es_snapshot, 2, &keys[0]);
+ else if (has_ubound)
+ scan = heap_beginscan(relation, estate->es_snapshot, 1, &keys[0]);
+ else if (has_lbound)
+ scan = heap_beginscan(relation, estate->es_snapshot, 1, &keys[1]);
+ else
+ scan = heap_beginscan(relation, estate->es_snapshot, 0, NULL);
+
+ /* Seek the starting position, if possible */
+ if (direction == ForwardScanDirection && has_lbound)
+ {
+ BlockNumber blknum = Min(BlockIdGetBlockNumber(&ip_min.ip_blkid),
+ scan->rs_nblocks - 1);
+ scan->rs_startblock = blknum;
+ }
+ else if (direction == BackwardScanDirection && has_ubound)
+ {
+ BlockNumber blknum = Min(BlockIdGetBlockNumber(&ip_max.ip_blkid),
+ scan->rs_nblocks - 1);
+ scan->rs_startblock = blknum;
+ }
+ ctss->cps.ss.ss_currentScanDesc = scan;
+}
+
+/*
+ * CTidAccessCustomScan
+ *
+ * Access method of ExecCtidScan below. It fetches a tuple from the underlying
+ * heap scan that was started from the point according to the tid clauses.
+ */
+static TupleTableSlot *
+CTidAccessCustomScan(CustomPlanState *node)
+{
+ CtidScanState *ctss = (CtidScanState *) node;
+ HeapScanDesc scan;
+ TupleTableSlot *slot;
+ EState *estate = node->ss.ps.state;
+ ScanDirection direction = estate->es_direction;
+ HeapTuple tuple;
+
+ if (!ctss->cps.ss.ss_currentScanDesc)
+ ReScanCtidScan(node);
+ scan = ctss->cps.ss.ss_currentScanDesc;
+ Assert(scan != NULL);
+
+ /*
+ * get the next tuple from the table
+ */
+ tuple = heap_getnext(scan, direction);
+ if (!HeapTupleIsValid(tuple))
+ return NULL;
+
+ slot = ctss->cps.ss.ss_ScanTupleSlot;
+ ExecStoreTuple(tuple, slot, scan->rs_cbuf, false);
+
+ return slot;
+}
+
+static bool
+CTidRecheckCustomScan(CustomPlanState *node, TupleTableSlot *slot)
+{
+ return true;
+}
+
+/*
+ * ExecCtidScan - A method of CustomPlanState; that fetches a tuple
+ * from the relation, if exist anymore.
+ */
+static TupleTableSlot *
+ExecCtidScan(CustomPlanState *node)
+{
+ return ExecScan(&node->ss,
+ (ExecScanAccessMtd) CTidAccessCustomScan,
+ (ExecScanRecheckMtd) CTidRecheckCustomScan);
+}
+
+/*
+ * CTidEndCustomScan - A method of CustomPlanState; that closes heap and
+ * scan descriptor, and release other related resources.
+ */
+static void
+EndCtidScan(CustomPlanState *node)
+{
+ CtidScanState *ctss = (CtidScanState *)node;
+
+ if (ctss->cps.ss.ss_currentScanDesc)
+ heap_endscan(ctss->cps.ss.ss_currentScanDesc);
+}
+
+/*
+ * ExplanCtidScanTargetRel - A method of CustomPlanState; that output
+ * relation's name to be scanned.
+ */
+static void
+ExplanCtidScanTargetRel(CustomPlanState *node, ExplainState *es)
+{
+ CtidScanState *ctss = (CtidScanState *) node;
+ CtidScanPlan *ctid_plan = (CtidScanPlan *) ctss->cps.ss.ps.plan;
+ Index rti = ctid_plan->cplan.scan.scanrelid;
+ RangeTblEntry *rte;
+ char *objectname = NULL;
+ char *namespace = NULL;
+ char *refname;
+
+ /* logic copied from ExplainTargetRel */
+ rte = rt_fetch(rti, es->rtable);
+ refname = (char *) list_nth(es->rtable_names, rti - 1);
+ if (refname == NULL)
+ refname = rte->eref->aliasname;
+
+ Assert(rte->rtekind == RTE_RELATION);
+ objectname = get_rel_name(rte->relid);
+ if (es->verbose)
+ namespace = get_namespace_name(get_rel_namespace(rte->relid));
+
+ if (es->format == EXPLAIN_FORMAT_TEXT)
+ {
+ appendStringInfoString(es->str, " on");
+ if (namespace != NULL)
+ appendStringInfo(es->str, " %s.%s", quote_identifier(namespace),
+ quote_identifier(objectname));
+ else if (objectname != NULL)
+ appendStringInfo(es->str, " %s", quote_identifier(objectname));
+ if (objectname == NULL || strcmp(refname, objectname) != 0)
+ appendStringInfo(es->str, " %s", quote_identifier(refname));
+ }
+ else
+ {
+ if (objectname != NULL)
+ ExplainPropertyText("Relation Name", objectname, es);
+ if (namespace != NULL)
+ ExplainPropertyText("Schema", namespace, es);
+ ExplainPropertyText("Alias", refname, es);
+ }
+}
+
+/*
+ * ExplainCtidScan - A method of CustomPlanState; that shows extra info
+ * on EXPLAIN command.
+ */
+static void
+ExplainCtidScan(CustomPlanState *node, List *ancestors, ExplainState *es)
+{
+ CtidScanState *ctss = (CtidScanState *) node;
+ CtidScanPlan *ctid_plan = (CtidScanPlan *) ctss->cps.ss.ps.plan;
+
+ /* logic copied from show_qual and show_expression */
+ if (ctid_plan->ctid_quals)
+ {
+ bool useprefix = es->verbose;
+ Node *qual;
+ List *context;
+ char *exprstr;
+
+ /* Convert AND list to explicit AND */
+ qual = (Node *) make_ands_explicit(ctid_plan->ctid_quals);
+
+ /* Set up deparsing context */
+ context = deparse_context_for_planstate((Node *)&node->ss.ps,
+ ancestors,
+ es->rtable,
+ es->rtable_names);
+
+ /* Deparse the expression */
+ exprstr = deparse_expression(qual, context, useprefix, false);
+
+ /* And add to es->str */
+ ExplainPropertyText("ctid quals", exprstr, es);
+ }
+}
+
+/*
+ * ExplainCtidPreScanNode - A method of CustomPlanState; that informs
+ * the core backend relation's rtindex to be referenced, prior to the
+ * main EXPLAIN processing.
+ */
+static void
+ExplainCtidPreScanNode(CustomPlanState *node, Bitmapset **rels_used)
+{
+ CtidScanState *ctss = (CtidScanState *) node;
+ Index scanrelid = ((Scan *)ctss->cps.ss.ps.plan)->scanrelid;
+
+ *rels_used = bms_add_member(*rels_used, scanrelid);
+}
+
+/*
+ * Entrypoint of this extension
+ */
+Datum
+CtidScanAddPath(PG_FUNCTION_ARGS)
+{
+ customScanArg *cscan_arg = (customScanArg *)PG_GETARG_POINTER(0);
+ PlannerInfo *root;
+ RangeTblEntry *rte;
+ RelOptInfo *baserel;
+ char relkind;
+ ListCell *lc;
+ List *ctid_quals = NIL;
+
+ if (cscan_arg->cpp_class != CUSTOMPLAN_CLASS_SCAN)
+ PG_RETURN_VOID();
+
+ root = cscan_arg->root;
+ rte = cscan_arg->rte;
+ baserel = cscan_arg->baserel;
+
+ /* all we can support is regular relations */
+ if (rte->rtekind != RTE_RELATION)
+ PG_RETURN_VOID();
+
+ relkind = get_rel_relkind(rte->relid);
+ if (relkind != RELKIND_RELATION &&
+ relkind != RELKIND_MATVIEW &&
+ relkind != RELKIND_TOASTVALUE)
+ PG_RETURN_VOID();
+
+ /* walk on the restrict info */
+ foreach (lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+ List *temp;
+
+ if (!IsA(rinfo, RestrictInfo))
+ continue; /* probably should never happen */
+ temp = CTidQualFromExpr((Node *) rinfo->clause, baserel->relid);
+ ctid_quals = list_concat(ctid_quals, temp);
+ }
+
+ /*
+ * OK, it is case when a part of restriction clause makes sense to
+ * reduce number of tuples, so we will add a custom scan path being
+ * provided by this module.
+ */
+ if (ctid_quals != NIL)
+ {
+ CtidScanPath *ctid_path;
+ Relids required_outer;
+
+ /*
+ * We don't support pushing join clauses into the quals of a ctidscan,
+ * but it could still have required parameterization due to LATERAL
+ * refs in its tlist.
+ */
+ required_outer = baserel->lateral_relids;
+
+ ctid_path = palloc0(sizeof(CtidScanPath));
+ ctid_path->cpath.path.type = T_CustomPath;
+ ctid_path->cpath.path.pathtype = T_CustomPlan;
+ ctid_path->cpath.path.parent = baserel;
+ ctid_path->cpath.path.param_info
+ = get_baserel_parampathinfo(root, baserel, required_outer);
+ ctid_path->cpath.methods = &ctidscan_path_methods;
+ ctid_path->cpath.flags = CPPFLAG_SUPPORT_BACKWARD_SCAN;
+ ctid_path->ctid_quals = ctid_quals;
+
+ CTidEstimateCosts(root, baserel, ctid_path);
+
+ add_path(baserel, &ctid_path->cpath.path);
+ }
+ PG_RETURN_VOID();
+}
+PG_FUNCTION_INFO_V1(CtidScanAddPath);
+
+/*
+ * Entrypoint of this extension
+ */
+void
+_PG_init(void)
+{
+ /* setup ctidscan_path_methods */
+ ctidscan_path_methods.CustomName = "ctidscan";
+ ctidscan_path_methods.CreateCustomPlan = CreateCtidScanPlan;
+ ctidscan_path_methods.TextOutCustomPath = TextOutCtidScanPath;
+
+ /* setup ctidscan_plan_methods */
+ ctidscan_plan_methods.CustomName = "ctidscan";
+ ctidscan_plan_methods.InitCustomPlan = InitCtidScanPlan;
+ ctidscan_plan_methods.SetCustomPlanRef = SetCtidScanPlanRef;
+ ctidscan_plan_methods.FinalizeCustomPlan = FinalizeCtidScanPlan;
+ ctidscan_plan_methods.CreateCustomPlanState = CreateCtidScanState;
+ ctidscan_plan_methods.TextOutCustomPlan = TextOutCtidScanPlan;
+ ctidscan_plan_methods.CopyCustomPlan = CopyCtidScanPlan;
+
+ /* setup ctidscan_planstate_methods */
+ ctidscan_exec_methods.CustomName = "ctidscan";
+ ctidscan_exec_methods.BeginCustomPlan = BeginCtidScan;
+ ctidscan_exec_methods.ExecCustomPlan = ExecCtidScan;
+ ctidscan_exec_methods.EndCustomPlan = EndCtidScan;
+ ctidscan_exec_methods.ReScanCustomPlan = ReScanCtidScan;
+ ctidscan_exec_methods.MarkPosCustomPlan = NULL;
+ ctidscan_exec_methods.RestrPosCustomPlan = NULL;
+ ctidscan_exec_methods.ExplainCustomPlanTargetRel = ExplanCtidScanTargetRel;
+ ctidscan_exec_methods.ExplainCustomPlan = ExplainCtidScan;
+ ctidscan_exec_methods.ExplainCustomPreScanNode = ExplainCtidPreScanNode;
+ ctidscan_exec_methods.GetSpecialCustomVar = NULL;
+}
diff --git a/contrib/ctidscan/ctidscan.control b/contrib/ctidscan/ctidscan.control
new file mode 100644
index 0000000..ad63432
--- /dev/null
+++ b/contrib/ctidscan/ctidscan.control
@@ -0,0 +1,5 @@
+# ctidscan extension
+comment = 'example implementation for custom-plan interface'
+default_version = '1.0'
+module_pathname = '$libdir/ctidscan'
+relocatable = true
diff --git a/contrib/ctidscan/expected/ctidscan.out b/contrib/ctidscan/expected/ctidscan.out
new file mode 100644
index 0000000..74d66e6
--- /dev/null
+++ b/contrib/ctidscan/expected/ctidscan.out
@@ -0,0 +1,312 @@
+--
+-- Regression Tests for Custom Plan APIs
+--
+CREATE EXTENSION ctidscan;
+-- construction of test data
+SET client_min_messages TO 'warning';
+CREATE SCHEMA regtest_custom_scan;
+SET search_path TO regtest_custom_scan, public;
+CREATE TABLE t1 (
+ a int primary key,
+ b text
+);
+INSERT INTO t1 (SELECT s, md5(s::text) FROM generate_series(1,400) s);
+VACUUM ANALYZE t1;
+CREATE TABLE t2 (
+ x int primary key,
+ y text
+);
+INSERT INTO t2 (SELECT s, md5(s::text)||md5(s::text) FROM generate_series(1,400) s);
+VACUUM ANALYZE t2;
+RESET client_min_messages;
+--
+-- Check Plans if no special extension is loaded.
+--
+EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40;
+ QUERY PLAN
+--------------------------------
+ Index Scan using t1_pkey on t1
+ Index Cond: (a = 40)
+(2 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE b like '%789%';
+ QUERY PLAN
+--------------------------------
+ Seq Scan on t1
+ Filter: (b ~~ '%789%'::text)
+(2 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid = '(2,10)'::tid;
+ QUERY PLAN
+------------------------------------
+ Tid Scan on t1
+ TID Cond: (ctid = '(2,10)'::tid)
+(2 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+ QUERY PLAN
+----------------------------------------------------------------------
+ Custom (ctidscan) on t1
+ Filter: ((ctid >= '(2,115)'::tid) AND (ctid <= '(3,10)'::tid))
+ ctid quals: ((ctid >= '(2,115)'::tid) AND (ctid <= '(3,10)'::tid))
+(3 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40;
+ QUERY PLAN
+--------------------------------
+ Index Scan using t1_pkey on t1
+ Index Cond: (a = 40)
+(2 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE b like '%789%';
+ QUERY PLAN
+--------------------------------
+ Seq Scan on t1
+ Filter: (b ~~ '%789%'::text)
+(2 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid = '(2,10)'::tid;
+ QUERY PLAN
+------------------------------------
+ Tid Scan on t1
+ TID Cond: (ctid = '(2,10)'::tid)
+(2 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+ QUERY PLAN
+----------------------------------------------------------------------
+ Custom (ctidscan) on t1
+ Filter: ((ctid >= '(2,115)'::tid) AND (ctid <= '(3,10)'::tid))
+ ctid quals: ((ctid >= '(2,115)'::tid) AND (ctid <= '(3,10)'::tid))
+(3 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 JOIN t2 ON t1.ctid = t2.ctid WHERE t1.ctid < '(2,10)'::tid AND t2.ctid > '(1,75)'::tid;
+ QUERY PLAN
+--------------------------------------------------
+ Merge Join
+ Merge Cond: (t1.ctid = t2.ctid)
+ -> Sort
+ Sort Key: t1.ctid
+ -> Custom (ctidscan) on t1
+ Filter: (ctid < '(2,10)'::tid)
+ ctid quals: (ctid < '(2,10)'::tid)
+ -> Sort
+ Sort Key: t2.ctid
+ -> Custom (ctidscan) on t2
+ Filter: (ctid > '(1,75)'::tid)
+ ctid quals: (ctid > '(1,75)'::tid)
+(12 rows)
+
+SELECT ctid,* FROM t1 WHERE ctid < '(1,20)'::tid;
+ ctid | a | b
+---------+-----+----------------------------------
+ (0,1) | 1 | c4ca4238a0b923820dcc509a6f75849b
+ (0,2) | 2 | c81e728d9d4c2f636f067f89cc14862c
+ (0,3) | 3 | eccbc87e4b5ce2fe28308fd9f2a7baf3
+ (0,4) | 4 | a87ff679a2f3e71d9181a67b7542122c
+ (0,5) | 5 | e4da3b7fbbce2345d7772b0674a318d5
+ (0,6) | 6 | 1679091c5a880faf6fb5e6087eb1b2dc
+ (0,7) | 7 | 8f14e45fceea167a5a36dedd4bea2543
+ (0,8) | 8 | c9f0f895fb98ab9159f51fd0297e236d
+ (0,9) | 9 | 45c48cce2e2d7fbdea1afc51c7c6ad26
+ (0,10) | 10 | d3d9446802a44259755d38e6d163e820
+ (0,11) | 11 | 6512bd43d9caa6e02c990b0a82652dca
+ (0,12) | 12 | c20ad4d76fe97759aa27a0c99bff6710
+ (0,13) | 13 | c51ce410c124a10e0db5e4b97fc2af39
+ (0,14) | 14 | aab3238922bcc25a6f606eb525ffdc56
+ (0,15) | 15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3
+ (0,16) | 16 | c74d97b01eae257e44aa9d5bade97baf
+ (0,17) | 17 | 70efdf2ec9b086079795c442636b55fb
+ (0,18) | 18 | 6f4922f45568161a8cdf4ad2299f6d23
+ (0,19) | 19 | 1f0e3dad99908345f7439f8ffabdffc4
+ (0,20) | 20 | 98f13708210194c475687be6106a3b84
+ (0,21) | 21 | 3c59dc048e8850243be8079a5c74d079
+ (0,22) | 22 | b6d767d2f8ed5d21a44b0e5886680cb9
+ (0,23) | 23 | 37693cfc748049e45d87b8c7d8b9aacd
+ (0,24) | 24 | 1ff1de774005f8da13f42943881c655f
+ (0,25) | 25 | 8e296a067a37563370ded05f5a3bf3ec
+ (0,26) | 26 | 4e732ced3463d06de0ca9a15b6153677
+ (0,27) | 27 | 02e74f10e0327ad868d138f2b4fdd6f0
+ (0,28) | 28 | 33e75ff09dd601bbe69f351039152189
+ (0,29) | 29 | 6ea9ab1baa0efb9e19094440c317e21b
+ (0,30) | 30 | 34173cb38f07f89ddbebc2ac9128303f
+ (0,31) | 31 | c16a5320fa475530d9583c34fd356ef5
+ (0,32) | 32 | 6364d3f0f495b6ab9dcf8d3b5c6e0b01
+ (0,33) | 33 | 182be0c5cdcd5072bb1864cdee4d3d6e
+ (0,34) | 34 | e369853df766fa44e1ed0ff613f563bd
+ (0,35) | 35 | 1c383cd30b7c298ab50293adfecb7b18
+ (0,36) | 36 | 19ca14e7ea6328a42e0eb13d585e4c22
+ (0,37) | 37 | a5bfc9e07964f8dddeb95fc584cd965d
+ (0,38) | 38 | a5771bce93e200c36f7cd9dfd0e5deaa
+ (0,39) | 39 | d67d8ab4f4c10bf22aa353e27879133c
+ (0,40) | 40 | d645920e395fedad7bbbed0eca3fe2e0
+ (0,41) | 41 | 3416a75f4cea9109507cacd8e2f2aefc
+ (0,42) | 42 | a1d0c6e83f027327d8461063f4ac58a6
+ (0,43) | 43 | 17e62166fc8586dfa4d1bc0e1742c08b
+ (0,44) | 44 | f7177163c833dff4b38fc8d2872f1ec6
+ (0,45) | 45 | 6c8349cc7260ae62e3b1396831a8398f
+ (0,46) | 46 | d9d4f495e875a2e075a1a4a6e1b9770f
+ (0,47) | 47 | 67c6a1e7ce56d3d6fa748ab6d9af3fd7
+ (0,48) | 48 | 642e92efb79421734881b53e1e1b18b6
+ (0,49) | 49 | f457c545a9ded88f18ecee47145a72c0
+ (0,50) | 50 | c0c7c76d30bd3dcaefc96f40275bdc0a
+ (0,51) | 51 | 2838023a778dfaecdc212708f721b788
+ (0,52) | 52 | 9a1158154dfa42caddbd0694a4e9bdc8
+ (0,53) | 53 | d82c8d1619ad8176d665453cfb2e55f0
+ (0,54) | 54 | a684eceee76fc522773286a895bc8436
+ (0,55) | 55 | b53b3a3d6ab90ce0268229151c9bde11
+ (0,56) | 56 | 9f61408e3afb633e50cdf1b20de6f466
+ (0,57) | 57 | 72b32a1f754ba1c09b3695e0cb6cde7f
+ (0,58) | 58 | 66f041e16a60928b05a7e228a89c3799
+ (0,59) | 59 | 093f65e080a295f8076b1c5722a46aa2
+ (0,60) | 60 | 072b030ba126b2f4b2374f342be9ed44
+ (0,61) | 61 | 7f39f8317fbdb1988ef4c628eba02591
+ (0,62) | 62 | 44f683a84163b3523afe57c2e008bc8c
+ (0,63) | 63 | 03afdbd66e7929b125f8597834fa83a4
+ (0,64) | 64 | ea5d2f1c4608232e07d3aa3d998e5135
+ (0,65) | 65 | fc490ca45c00b1249bbe3554a4fdf6fb
+ (0,66) | 66 | 3295c76acbf4caaed33c36b1b5fc2cb1
+ (0,67) | 67 | 735b90b4568125ed6c3f678819b6e058
+ (0,68) | 68 | a3f390d88e4c41f2747bfa2f1b5f87db
+ (0,69) | 69 | 14bfa6bb14875e45bba028a21ed38046
+ (0,70) | 70 | 7cbbc409ec990f19c78c75bd1e06f215
+ (0,71) | 71 | e2c420d928d4bf8ce0ff2ec19b371514
+ (0,72) | 72 | 32bb90e8976aab5298d5da10fe66f21d
+ (0,73) | 73 | d2ddea18f00665ce8623e36bd4e3c7c5
+ (0,74) | 74 | ad61ab143223efbc24c7d2583be69251
+ (0,75) | 75 | d09bf41544a3365a46c9077ebb5e35c3
+ (0,76) | 76 | fbd7939d674997cdb4692d34de8633c4
+ (0,77) | 77 | 28dd2c7955ce926456240b2ff0100bde
+ (0,78) | 78 | 35f4a8d465e6e1edc05f3d8ab658c551
+ (0,79) | 79 | d1fe173d08e959397adf34b1d77e88d7
+ (0,80) | 80 | f033ab37c30201f73f142449d037028d
+ (0,81) | 81 | 43ec517d68b6edd3015b3edc9a11367b
+ (0,82) | 82 | 9778d5d219c5080b9a6a17bef029331c
+ (0,83) | 83 | fe9fc289c3ff0af142b6d3bead98a923
+ (0,84) | 84 | 68d30a9594728bc39aa24be94b319d21
+ (0,85) | 85 | 3ef815416f775098fe977004015c6193
+ (0,86) | 86 | 93db85ed909c13838ff95ccfa94cebd9
+ (0,87) | 87 | c7e1249ffc03eb9ded908c236bd1996d
+ (0,88) | 88 | 2a38a4a9316c49e5a833517c45d31070
+ (0,89) | 89 | 7647966b7343c29048673252e490f736
+ (0,90) | 90 | 8613985ec49eb8f757ae6439e879bb2a
+ (0,91) | 91 | 54229abfcfa5649e7003b83dd4755294
+ (0,92) | 92 | 92cc227532d17e56e07902b254dfad10
+ (0,93) | 93 | 98dce83da57b0395e163467c9dae521b
+ (0,94) | 94 | f4b9ec30ad9f68f89b29639786cb62ef
+ (0,95) | 95 | 812b4ba287f5ee0bc9d43bbf5bbe87fb
+ (0,96) | 96 | 26657d5ff9020d2abefe558796b99584
+ (0,97) | 97 | e2ef524fbf3d9fe611d5a8e90fefdc9c
+ (0,98) | 98 | ed3d2c21991e3bef5e069713af9fa6ca
+ (0,99) | 99 | ac627ab1ccbdb62ec96e702f07f6425b
+ (0,100) | 100 | f899139df5e1059396431415e770c6dd
+ (0,101) | 101 | 38b3eff8baf56627478ec76a704e9b52
+ (0,102) | 102 | ec8956637a99787bd197eacd77acce5e
+ (0,103) | 103 | 6974ce5ac660610b44d9b9fed0ff9548
+ (0,104) | 104 | c9e1074f5b3f9fc8ea15d152add07294
+ (0,105) | 105 | 65b9eea6e1cc6bb9f0cd2a47751a186f
+ (0,106) | 106 | f0935e4cd5920aa6c7c996a5ee53a70f
+ (0,107) | 107 | a97da629b098b75c294dffdc3e463904
+ (0,108) | 108 | a3c65c2974270fd093ee8a9bf8ae7d0b
+ (0,109) | 109 | 2723d092b63885e0d7c260cc007e8b9d
+ (0,110) | 110 | 5f93f983524def3dca464469d2cf9f3e
+ (0,111) | 111 | 698d51a19d8a121ce581499d7b701668
+ (0,112) | 112 | 7f6ffaa6bb0b408017b62254211691b5
+ (0,113) | 113 | 73278a4a86960eeb576a8fd4c9ec6997
+ (0,114) | 114 | 5fd0b37cd7dbbb00f97ba6ce92bf5add
+ (0,115) | 115 | 2b44928ae11fb9384c4cf38708677c48
+ (0,116) | 116 | c45147dee729311ef5b5c3003946c48f
+ (0,117) | 117 | eb160de1de89d9058fcb0b968dbbbd68
+ (0,118) | 118 | 5ef059938ba799aaa845e1c2e8a762bd
+ (0,119) | 119 | 07e1cd7dca89a1678042477183b7ac3f
+ (0,120) | 120 | da4fb5c6e93e74d3df8527599fa62642
+ (1,1) | 121 | 4c56ff4ce4aaf9573aa5dff913df997a
+ (1,2) | 122 | a0a080f42e6f13b3a2df133f073095dd
+ (1,3) | 123 | 202cb962ac59075b964b07152d234b70
+ (1,4) | 124 | c8ffe9a587b126f152ed3d89a146b445
+ (1,5) | 125 | 3def184ad8f4755ff269862ea77393dd
+ (1,6) | 126 | 069059b7ef840f0c74a814ec9237b6ec
+ (1,7) | 127 | ec5decca5ed3d6b8079e2e7e7bacc9f2
+ (1,8) | 128 | 76dc611d6ebaafc66cc0879c71b5db5c
+ (1,9) | 129 | d1f491a404d6854880943e5c3cd9ca25
+ (1,10) | 130 | 9b8619251a19057cff70779273e95aa6
+ (1,11) | 131 | 1afa34a7f984eeabdbb0a7d494132ee5
+ (1,12) | 132 | 65ded5353c5ee48d0b7d48c591b8f430
+ (1,13) | 133 | 9fc3d7152ba9336a670e36d0ed79bc43
+ (1,14) | 134 | 02522a2b2726fb0a03bb19f2d8d9524d
+ (1,15) | 135 | 7f1de29e6da19d22b51c68001e7e0e54
+ (1,16) | 136 | 42a0e188f5033bc65bf8d78622277c4e
+ (1,17) | 137 | 3988c7f88ebcb58c6ce932b957b6f332
+ (1,18) | 138 | 013d407166ec4fa56eb1e1f8cbe183b9
+ (1,19) | 139 | e00da03b685a0dd18fb6a08af0923de0
+(139 rows)
+
+SELECT ctid,* FROM t1 WHERE ctid > '(4,0)'::tid;
+ ctid | a | b
+------+---+---
+(0 rows)
+
+SELECT ctid,* FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+ ctid | a | b
+---------+-----+----------------------------------
+ (2,115) | 355 | 82cec96096d4281b7c95cd7e74623496
+ (2,116) | 356 | 6c524f9d5d7027454a783c841250ba71
+ (2,117) | 357 | fb7b9ffa5462084c5f4e7e85a093e6d7
+ (2,118) | 358 | aa942ab2bfa6ebda4840e7360ce6e7ef
+ (2,119) | 359 | c058f544c737782deacefa532d9add4c
+ (2,120) | 360 | e7b24b112a44fdd9ee93bdf998c6ca0e
+ (3,1) | 361 | 52720e003547c70561bf5e03b95aa99f
+ (3,2) | 362 | c3e878e27f52e2a57ace4d9a76fd9acf
+ (3,3) | 363 | 00411460f7c92d2124a67ea0f4cb5f85
+ (3,4) | 364 | bac9162b47c56fc8a4d2a519803d51b3
+ (3,5) | 365 | 9be40cee5b0eee1462c82c6964087ff9
+ (3,6) | 366 | 5ef698cd9fe650923ea331c15af3b160
+ (3,7) | 367 | 05049e90fa4f5039a8cadc6acbb4b2cc
+ (3,8) | 368 | cf004fdc76fa1a4f25f62e0eb5261ca3
+ (3,9) | 369 | 0c74b7f78409a4022a2c4c5a5ca3ee19
+ (3,10) | 370 | d709f38ef758b5066ef31b18039b8ce5
+(16 rows)
+
+SELECT t1.ctid,* FROM t1 JOIN t2 ON t1.ctid = t2.ctid WHERE t1.ctid < '(2,10)'::tid AND t2.ctid > '(1,75)'::tid;
+ ctid | a | b | x | y
+--------+-----+----------------------------------+-----+------------------------------------------------------------------
+ (1,76) | 196 | 084b6fbb10729ed4da8c3d3f5a3ae7c9 | 157 | 6c4b761a28b734fe93831e3fb400ce876c4b761a28b734fe93831e3fb400ce87
+ (1,77) | 197 | 85d8ce590ad8981ca2c8286f79f59954 | 158 | 06409663226af2f3114485aa4e0a23b406409663226af2f3114485aa4e0a23b4
+ (1,78) | 198 | 0e65972dce68dad4d52d063967f0a705 | 159 | 140f6969d5213fd0ece03148e62e461e140f6969d5213fd0ece03148e62e461e
+ (1,79) | 199 | 84d9ee44e457ddef7f2c4f25dc8fa865 | 160 | b73ce398c39f506af761d2277d853a92b73ce398c39f506af761d2277d853a92
+ (1,80) | 200 | 3644a684f98ea8fe223c713b77189a77 | 161 | bd4c9ab730f5513206b999ec0d90d1fbbd4c9ab730f5513206b999ec0d90d1fb
+ (1,81) | 201 | 757b505cfd34c64c85ca5b5690ee5293 | 162 | 82aa4b0af34c2313a562076992e50aa382aa4b0af34c2313a562076992e50aa3
+ (2,1) | 241 | f340f1b1f65b6df5b5e3f94d95b11daf | 163 | 0777d5c17d4066b82ab86dff8a46af6f0777d5c17d4066b82ab86dff8a46af6f
+ (2,2) | 242 | e4a6222cdb5b34375400904f03d8e6a5 | 164 | fa7cdfad1a5aaf8370ebeda47a1ff1c3fa7cdfad1a5aaf8370ebeda47a1ff1c3
+ (2,3) | 243 | cb70ab375662576bd1ac5aaf16b3fca4 | 165 | 9766527f2b5d3e95d4a733fcfb77bd7e9766527f2b5d3e95d4a733fcfb77bd7e
+ (2,4) | 244 | 9188905e74c28e489b44e954ec0b9bca | 166 | 7e7757b1e12abcb736ab9a754ffb617a7e7757b1e12abcb736ab9a754ffb617a
+ (2,5) | 245 | 0266e33d3f546cb5436a10798e657d97 | 167 | 5878a7ab84fb43402106c575658472fa5878a7ab84fb43402106c575658472fa
+ (2,6) | 246 | 38db3aed920cf82ab059bfccbd02be6a | 168 | 006f52e9102a8d3be2fe5614f42ba989006f52e9102a8d3be2fe5614f42ba989
+ (2,7) | 247 | 3cec07e9ba5f5bb252d13f5f431e4bbb | 169 | 3636638817772e42b59d74cff571fbb33636638817772e42b59d74cff571fbb3
+ (2,8) | 248 | 621bf66ddb7c962aa0d22ac97d69b793 | 170 | 149e9677a5989fd342ae44213df68868149e9677a5989fd342ae44213df68868
+ (2,9) | 249 | 077e29b11be80ab57e1a2ecabb7da330 | 171 | a4a042cf4fd6bfb47701cbc8a1653adaa4a042cf4fd6bfb47701cbc8a1653ada
+(15 rows)
+
+PREPARE p1(tid, tid) AS SELECT ctid,* FROM t1
+ WHERE b like '%abc%' AND ctid BETWEEN $1 AND $2;
+EXPLAIN (costs off) EXECUTE p1('(5,0)'::tid, '(10,0)'::tid);
+ QUERY PLAN
+-----------------------------------------------------------------------------------------
+ Custom (ctidscan) on t1
+ Filter: ((b ~~ '%abc%'::text) AND (ctid >= '(5,0)'::tid) AND (ctid <= '(10,0)'::tid))
+ ctid quals: ((ctid >= '(5,0)'::tid) AND (ctid <= '(10,0)'::tid))
+(3 rows)
+
+EXPLAIN (costs off) EXECUTE p1('(10,0)'::tid, '(5,0)'::tid);
+ QUERY PLAN
+-----------------------------------------------------------------------------------------
+ Custom (ctidscan) on t1
+ Filter: ((b ~~ '%abc%'::text) AND (ctid >= '(10,0)'::tid) AND (ctid <= '(5,0)'::tid))
+ ctid quals: ((ctid >= '(10,0)'::tid) AND (ctid <= '(5,0)'::tid))
+(3 rows)
+
+-- Test cleanup
+DROP SCHEMA regtest_custom_scan CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table t1
+drop cascades to table t2
diff --git a/contrib/ctidscan/sql/ctidscan.sql b/contrib/ctidscan/sql/ctidscan.sql
new file mode 100644
index 0000000..213a97a
--- /dev/null
+++ b/contrib/ctidscan/sql/ctidscan.sql
@@ -0,0 +1,54 @@
+--
+-- Regression Tests for Custom Plan APIs
+--
+
+CREATE EXTENSION ctidscan;
+
+-- construction of test data
+SET client_min_messages TO 'warning';
+
+CREATE SCHEMA regtest_custom_scan;
+
+SET search_path TO regtest_custom_scan, public;
+
+CREATE TABLE t1 (
+ a int primary key,
+ b text
+);
+INSERT INTO t1 (SELECT s, md5(s::text) FROM generate_series(1,400) s);
+VACUUM ANALYZE t1;
+
+CREATE TABLE t2 (
+ x int primary key,
+ y text
+);
+INSERT INTO t2 (SELECT s, md5(s::text)||md5(s::text) FROM generate_series(1,400) s);
+VACUUM ANALYZE t2;
+
+RESET client_min_messages;
+--
+-- Check Plans if no special extension is loaded.
+--
+EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE b like '%789%';
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid = '(2,10)'::tid;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE b like '%789%';
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid = '(2,10)'::tid;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+EXPLAIN (costs off) SELECT * FROM t1 JOIN t2 ON t1.ctid = t2.ctid WHERE t1.ctid < '(2,10)'::tid AND t2.ctid > '(1,75)'::tid;
+
+SELECT ctid,* FROM t1 WHERE ctid < '(1,20)'::tid;
+SELECT ctid,* FROM t1 WHERE ctid > '(4,0)'::tid;
+SELECT ctid,* FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+SELECT t1.ctid,* FROM t1 JOIN t2 ON t1.ctid = t2.ctid WHERE t1.ctid < '(2,10)'::tid AND t2.ctid > '(1,75)'::tid;
+
+PREPARE p1(tid, tid) AS SELECT ctid,* FROM t1
+ WHERE b like '%abc%' AND ctid BETWEEN $1 AND $2;
+EXPLAIN (costs off) EXECUTE p1('(5,0)'::tid, '(10,0)'::tid);
+EXPLAIN (costs off) EXECUTE p1('(10,0)'::tid, '(5,0)'::tid);
+
+-- Test cleanup
+DROP SCHEMA regtest_custom_scan CASCADE;
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index b4a06e4..3d7a289 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -109,6 +109,11 @@
+ pg_custom_plan_provider
+ custom plan providers
+
+
+ pg_conversionencoding conversion information
@@ -2508,6 +2513,60 @@
+
+ pg_custom_plan_provider
+
+
+ pg_custom_plan_provider
+
+
+
+ The catalog pg_custom_plan_provider describes
+ custom-plan providers. See
+ for more information.
+
+
+
+ pg_custom_plan_provider> Columns
+
+
+
+ Name
+ Type
+ References
+ Description
+
+
+
+
+ oid
+ oid
+
+ Row identifier (hidden attribute; must be explicitly selected)
+
+
+ cppname
+ name
+
+ custom-plan provider name
+
+
+ cppclass
+ char
+
+ class of custom-plan node on which this custom-plan provider can perform
+
+
+ cpphandler
+ regproc
+ pg_proc.oid
+ handler function of custom-plan provder that will propose an alternative query execution path
+
+
+
+
+
+
pg_database
diff --git a/doc/src/sgml/custom-plan-provider.sgml b/doc/src/sgml/custom-plan-provider.sgml
new file mode 100644
index 0000000..1d4a9c3
--- /dev/null
+++ b/doc/src/sgml/custom-plan-provider.sgml
@@ -0,0 +1,413 @@
+
+
+
+ Writing A Custom Plan Provider
+
+ custom plan provider
+ handler for
+
+
+
+ PostgreSQL has variable kind of built-in plan nodes that implement
+ a particular portion to process the supplied SQL queries.
+ For example, SeqScan node implements full table scan, HashJoin node
+ implements tables join using a hash table and so on.
+
+
+ The custom-plan interface allows extensions to provide alternative
+ query execution paths, in addition to the built-in ones. Query planner
+ will choose the cheapest path towards a particular relation(s) scan or
+ join in all the proposed ones by built-in and extensions.
+ Once a proposed custom-plan got picked up, callback functions associated
+ with the node shall be called and extension will get control to process
+ the task. We call the extension that provides custom-plan custom plan
+ provider.
+
+
+
+ Custom Plan Overall Steps
+
+ A custom plan provider can be registered using
+ command.
+ It takes a class of custom plan and its handler function.
+ Class of custom plan specifies the task to be replaced by the custom-
+ plan being defined.
+ Only SCAN> is the supported class of custom-plan right now.
+ Custom plan handler function is an entrypoint to the planner. It calls
+ the handler function during construction of query execution path.
+
+
+ Handler function has to be declared as a function that takes one
+ internal> data type and returns void>.
+ On invocation, query planner delivers a pointer of data structure
+ according to the custom plan class, custom-plan handler function will
+ decide whether it can offer alternative execution path towards the
+ required task. If available, it can add a CustomPath>
+ (or inherited object type) as one of the candidate execution path
+ with estimated cost and set of callbacks defined in the
+ CustomPathMethods> structure.
+
+
+ Planner compares all the potential execution paths based on its cost.
+ Once a custom path that was added by custom plan provider gets chosen,
+ CreateCustomPlan> callback shall be called, to populate
+ a CustomPlan> (or inherited object type) node according to
+ the CustomPath> node preliminary constructed.
+ In the similar manner, CreateCustomPlanState> callback shall
+ be called, to populate a CustomPlanState> (or inherited
+ object type) node according to the CustomPlan> node being
+ constructed above.
+ The reason why custom-plan provider has to allocate a node object by
+ itself is that we allow to extend the base types to store private
+ fields managed by individual custom-plan providers, thus only custom-
+ plan provider knows actual data size to be allocated.
+
+
+ Once a CustomPlanState> is constructed, its callback
+ functions are invoked by executor, so custom-plan provider performs
+ required tasks then pops up the result.
+
+
+
+
+ Custom Plan Handler Functions
+
+ A handler function that was specified on
+ the command is
+ declared to return void> data type and takes an
+ internal> data type; that is usually applied to exchange
+ internal data structure.
+ This handler function is not an exception. It can reference a pointer
+ being informed via function argument to understand the context.
+
+
+ The data structure of arguments fully depend on the class of custom-
+ plan. In case of SCAN> class (that is only supported one
+ right now), the first argument points the following data structure
+ that has enough information to construct a Path> node.
+
+typedef struct {
+ uint32 cpp_class;
+ PlannerInfo *root;
+ RelOptInfo *baserel;
+ RangeTblEntry *rte;
+} customScanArg;
+
+ cpp_class>is always CUSTOMPLAN_CLASS_SCAN>
+ that shows this structure is for scan class.
+ root> is PlannerInfo> of this plan,
+ baserel> is RelOptInfo> of the relation to
+ be scanned, and rte> is RangeTblEntry> of
+ the relation.
+
+
+ The custom-plan provider being invoked can check whether it can
+ provides alternative way to scan the relation. If available, it
+ shall construct CustomPath> or its inherited one with
+ estimated cost and callbacks below, then register the path using
+ add_path> towards the supplied RelOptInfo>.
+
+
+
+
+ Custom Path Callbacks
+
+ This section introduces callback functions of CustomPath>
+ structure; defined in the CustomPathMethods>.
+
+
+
+Node *
+CreateCustomPlan(PlannerInfo *root,
+ CustomPath *best_path);
+
+ It populates a CustomPlan> (or inherited data type) node
+ according to the supplied CustomPath> node which was
+ constructed on the custom plan handler function then chosen by the
+ query planner.
+ Only custom plan provider can know exact size of the node to be
+ allocated, this callback allocate a CustomPlan> node with
+ CustomPlanMethods> callbacks table and arbitrary private
+ fields.
+
+
+
+
+void
+TextOutCustomPath(StringInfo str, const CustomPath *node);
+
+ It makes a text representation of custom path node. If custom-plan
+ provider extends CustomPath> data type, it shall to put private
+ fields on the supplied StringInfo> with text form.
+ Note that common fields in CustomPath> are handled by backend,
+ so extension needs to do nothing special.
+
+
+
+
+ Custom Plan Callbacks
+
+ This section introduces callback functions of CustomPlan>
+ structure; defined in the CustomPlanMethods>.
+
+
+
+Plan *
+InitCustomPlan(CustomPlan *custom_plan,
+ PlannerInfo *root,
+ CustomPath *best_path,
+ List *tlist,
+ List *clauses);
+
+ It initializes the CustomPlan> node being acquired by
+ CreateCustomPlan> callback.
+ The backend takes some common initializations prior to its invocation.
+ tlist> and clauses> are extracted from the path
+ node according to the usual manner, so all custom plan provider has to
+ do is putting these members if nothing special are done.
+
+
+
+
+void
+SetCustomPlanRef(PlannerInfo *root,
+ CustomPlan *custom_plan,
+ int rtoffset);
+
+ It adjusts varno> and varattno> of var-nodes in
+ the expression tree chained from CustomPlan> node (including
+ private fields in the inherited data type).
+ In case of usual relation scan, rtoffset> shall be added to
+ varno> of var-nodes and scanrelid> of plan node,
+ then fix_scan_expr> shall be called on expression nodes to
+ track plan dependency.
+
+
+
+
+void
+FinalizeCustomPlan(PlannerInfo *root,
+ CustomPlan *custom_plan,
+ bool (*finalize_primnode)(),
+ void *finalize_context);
+
+ It is an optional callback, which applies finalize_primenode>
+ on the expression nodes of private fields because only custom-plan provider
+ knows what private fields are expression node to be finalized.
+ Note that backend applies finalize_primenode> on
+ the tlist> and qual> of the base plan node,
+ so custom-plan provider shall do nothing special, if it has no private
+ expression node.
+ Also, it handles recursive stuff on the lefttree> and
+ righttree>, so it does not need to handle recursive walks.
+
+
+
+
+Node *
+CreateCustomPlanState(CustomPlan *custom_plan);
+
+ It populates a CustomPlanState> (or inherited data type)
+ node according to the supplied CustomPlan> node preliminary
+ constructed, on the beginning of query executor.
+ Only custom plan provider can know exact size of the node to be
+ allocated, this callback allocate a CustomPlanState> node
+ with CustomExecMethods> callbacks table and arbitrary
+ private fields.
+
+
+ Note that main purpose of this callback is allocation of
+ CustomPlanState> node, not initialization of individual
+ fields because it shall be handled on the BeginCustomPlan>
+ callback to be invoked next to the common usual initialization.
+
+
+
+
+void
+TextOutCustomPlan(StringInfo str,
+ const CustomPlan *node);
+
+ It makes a text representation of custom plan node. If custom-plan
+ provider extends CustomPlan> data type, it shall put private
+ fields on the supplied StringInfo> with text form.
+ Note that common fields in CustomPlan> are handled by backend,
+ so extension needs to do nothing special.
+
+
+
+
+CustomPlan *
+CopyCustomPlan(const CustomPlan *from);
+
+ It duplicate a CustomPlan> node onto a newly allocated one.
+ If custom-plan provider extends the CustomPlan> node, it shall
+ copy the private fields and callback table but no need to copy the common
+ scan> field in the CustomPlan> data type.
+
+
+
+
+ Custom Executor Callbacks
+
+ This section introduces callback functions of CustomPlanState>
+ structure; defined in the CustomExecMethods>.
+
+
+
+void
+BeginCustomPlan(CustomPlanState *node,
+ EState *estate,
+ int eflags);
+
+ It begins execution of custom-plan. This callback is invoked during
+ executor startup to initialize the supplied CustomPlanState>
+ that was constructed on the CreateCustomPlanState> above.
+ The custom-plan provider shall have initialization of its private fields
+ and common fields within CustomPlanState> if needed, because
+ backend code already initializes expressions on its targetlist>
+ and qual>, assigns result slot according to the
+ targetlist> and also assigns scan slot if scanrelid>
+ is valid.
+
+
+
+
+TupleTableSlot *
+ExecCustomPlan(CustomPlanState *node);
+
+ It fetches one row from the custom-plan node, returning it in a tuple
+ table slot (ps_ResultTupleSlot> of PlanState>
+ shall be used) or NULL> if no more rows are available.
+ The tuple table slot infrastructure allows either a physical or virtual
+ tuple to be returned; in most cases the latter choice is preferable from
+ a performance standpoint.
+ The rows being returned have to match the tuple-descriptor of the
+ PlanState>
+
+
+ Note that this call is under a short-lived memory context that will be
+ reset for each invocation. So, it may be a good choice to switch
+ es_query_cxt> of the EState>, to acquire per-scan
+ duration memory.
+
+
+
+
+Node *
+MultiExecCustomPlan(CustomPlanState *node);
+
+ It allows to return arbitrary data structure to the upper node, unlike
+ usual ExecCustomPlan>. Built-in code has no invocation path
+ to call MultiExecProcNode> towards CustomPlanState>
+ node, so it is invoked only when a particular custom-plan provider made
+ a stacked custom-plan nodes and called MultiExecProcNode> to
+ this underlying node.
+
+
+ Note that it is custom-plan provider's responsibility to translate the
+ arbitrary data structure into PostgreSQL>'s complianced
+ data structure when top-level CustomPlanState> returns a row
+ using ExecCustomPlan>.
+
+
+
+
+void
+EndCustomPlan(CustomPlanState *node);
+
+ It ends the execution of custom plan and release any resources held by
+ this node. If custom-plan provider acquired resources that is not
+ released automatically at end of executor, it is responsibility of the
+ custom plan provider.
+
+
+
+
+void
+ReScanCustomPlan(CustomPlanState *node);
+
+ It restarts the scan from the beginning. Note that any parameters
+ the scan depends on may have changed value, so the new scan does not
+ necessarily return exactly the same rows.
+
+
+
+
+void *
+MarkPosCustomPlan(CustomPlanState *node);
+
+ It is an optional callback, to save the current position on somewhere
+ in the private field of CustomPlanState>, to restore the
+ position later.
+ If this method is valid, RestrPosCustomPlan> also has to
+ be valid.
+
+
+
+
+void *
+RestrPosCustomPlan(CustomPlanState *node);
+
+ It is an optional callback, to restore the previous position being saved
+ by MarkPosCustomPlan>.
+ If this method is valid, MarkPosCustomPlan> also has to
+ be valid.
+
+
+
+
+void
+ExplainCustomPlanTargetRel(CustomPlanState *node,
+ ExplainState *es);
+
+ It is an optional callback, to show the target relation to be scanned.
+ In most cases, custom plan provider put text representation of the relation
+ to be scanned according to the manner in ExplainTargetRel>.
+
+
+
+
+void
+ExplainCustomPlan(CustomPlanState *node,
+ List *ancestors,
+ ExplainState *es);
+
+ It is an optional callback, to show custom-plan specific explain output.
+
+
+
+
+void
+ExplainCustomPreScanNode(CustomPlanState *node,
+ Bitmapset **rels_used);
+
+ It is an optional callback, to inform the backend which relation is
+ referenced. It shall set scanrelid> of the target relation.
+ If NULL>, it means this custom-plan provider never
+ references base relations.
+
+
+
+
+Node *
+GetSpecialCustomVar(CustomPlanState *node,
+ Var *varnode,
+ PlanState **child_ps);
+
+ It is an optional callback, to solve references to special varno on
+ the CustomPlanState> when EXPLAIN> needs the
+ text form of the column actually referenced.
+ In case when custom-plan provider adjusted varno> of varnodes
+ on the expression tree to use special varnos (INNER_VAR>,
+ OUTER_VAR> or INDEX_VAR>), custom-plan provider has
+ to inform the backend which column is mapped on the underlying plan-state.
+
+
+ This callback is expected to return Var> node to reference
+ an actual variable on the underlying PlanState> that shall be
+ set on the child_ps> argument.
+
+
+
+
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 5902f97..a9b9efc 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -92,6 +92,7 @@
+
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
index 9bde108..4702178 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -242,6 +242,7 @@
&nls;
&plhandler;
&fdwhandler;
+ &custom-plan-provider;
&geqo;
&indexam;
&gist;
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index b685e16..ab35742 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -55,6 +55,7 @@ Complete list of usable sgml source files in this directory.
+
@@ -95,6 +96,7 @@ Complete list of usable sgml source files in this directory.
+
diff --git a/doc/src/sgml/ref/create_custom_plan_provider.sgml b/doc/src/sgml/ref/create_custom_plan_provider.sgml
new file mode 100644
index 0000000..0816584
--- /dev/null
+++ b/doc/src/sgml/ref/create_custom_plan_provider.sgml
@@ -0,0 +1,139 @@
+
+
+
+
+ CREATE CUSTOM PLAN PROVIDER
+
+
+
+ CREATE CUSTOM PLAN PROVIDER
+ 7
+ SQL - Language Statements
+
+
+
+ CREATE CUSTOM PLAN PROVIDER
+ define a new custom plan provider
+
+
+
+
+CREATE CUSTOM PLAN PROVIDER cpp_name FOR cpp_class
+ HANDLER handler_function
+
+
+
+
+ Description
+
+
+ CREATE CUSTOM PLAN PROVIDER defines a new custom-plan
+ provider.
+ The user who defines the custom-plan provider has to be a superuser.
+
+
+
+ A custom-plan provider can offer the query planner alternative options
+ to scan relation, or potentially join relations and so on, in addition
+ to the built-in logics. It is usually extension modules that implement
+ callbacks according to the custom-plan interface.
+
+
+ This statement defines a couple of an entrypoint of custom-plan provider
+ and its supporting workload type. Right now, scan is
+ the only class being supported; that enables to call extension's
+ callback during query execution instead of built-in routines like
+ SeqScan or IndexScan if its
+ cost estimation is enough reasonable.
+
+
+
+
+ Parameters
+
+
+
+ cpp_name
+
+
+ The name of the custom-plan provider to be created.
+
+
+
+
+
+ cpp_class
+
+
+ Workload type on which custom-plan provider can perform.
+ Only SCAN is supported option right now.
+
+
+
+
+
+ handler_function
+
+
+ A function to be called when query planner is finding the best path
+ to scan a relation.
+
+
+
+
+
+
+
+ Notes
+
+
+ The function that performs as a custom-plan provider shall be declared
+ to return void> and take one argument with internal>
+ data type.
+ The core PostgreSQL> calls custom-plan provider function
+ with a set of information about planner's state and relation(s) to be
+ scanned, then this function shall check whether it can offer alternative
+ scan paths or not.
+ If available, it constructs a path object derived from
+ CustomPath> structure, that contains a set of callbacks
+ including the ones to populate CustomPlan> or
+ CustomPlanState> object later.
+ If extension needs to save its private information in these object,
+ define a new structure that extends above data types.
+
+
+
+
+ Examples
+
+ Create a custom-plan provider ctidscan> that uses the funcion
+ ctidscanaddpath>.
+
+CREATE CUSTOM PLAN PROVIDER ctidscan FOR scan HANDLER ctidscanaddpath;
+
+
+
+
+
+ Compatibility
+
+ There is no CREATE CUSTOM PLAN command
+ in the SQL standard.
+
+
+
+
+ See Also
+
+
+
+
+
+
+
+
+
+
diff --git a/doc/src/sgml/ref/drop_custom_plan_provider.sgml b/doc/src/sgml/ref/drop_custom_plan_provider.sgml
new file mode 100644
index 0000000..6a305a8
--- /dev/null
+++ b/doc/src/sgml/ref/drop_custom_plan_provider.sgml
@@ -0,0 +1,108 @@
+
+
+
+
+ DROP CUSTOM PLAN PROVIDER
+
+
+
+ DROP CUSTOM PLAN PROVIDER
+ 7
+ SQL - Language Statements
+
+
+
+ DROP CUSTOM PLAN PROVIDER
+ remove a custom-plan provider
+
+
+
+
+DROP CUSTOM PLAN PROVIDER [ IF EXISTS ] cpp_name [ CASCADE | RESTRICT ]
+
+
+
+
+ Description
+
+
+ DROP CUSTOM PLAN PROVIDER removes an existing custom
+ plan provider. To execute this command, the current user must be superuser.
+
+
+
+
+ Parameters
+
+
+
+ IF EXISTS
+
+
+ Do not throw an error if the custom-plan provider does not exist.
+ A notice is issued in this case.
+
+
+
+
+
+ name
+
+
+ The name of an existing custom-plan provider.
+
+
+
+
+
+ CASCADE
+
+
+ Automatically drop objects that depend on the custom-plan provider.
+
+
+
+
+
+ RESTRICT
+
+
+ Refuse to drop the custom-plan provider if any objects depend on it.
+ This is the default.
+
+
+
+
+
+
+
+ Examples
+
+
+ Drop a custom-plan provider foo> if it exists:
+
+DROP CUSTOM PLAN PROVIDER IF EXISTS foo;
+
+
+
+
+ Compatibility
+
+
+ There is no DROP CUSTOM PLAN command
+ in the SQL standard.
+
+
+
+
+ See Also
+
+
+
+
+
+
+
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index 6ec1263..1a3dbdd 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -83,6 +83,7 @@
&createCast;
&createCollation;
&createConversion;
+ &createCustomPlanProvider;
&createDatabase;
&createDomain;
&createEventTrigger;
@@ -123,6 +124,7 @@
&dropCast;
&dropCollation;
&dropConversion;
+ &dropCustomPlanProvider;
&dropDatabase;
&dropDomain;
&dropEventTrigger;
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index a974bd5..f7e29eb 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -39,7 +39,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
pg_ts_parser.h pg_ts_template.h pg_extension.h \
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
- pg_foreign_table.h \
+ pg_foreign_table.h pg_custom_plan_provider.h \
pg_default_acl.h pg_seclabel.h pg_shseclabel.h pg_collation.h pg_range.h \
toasting.h indexing.h \
)
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index d41ba49..496bc9a 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -30,6 +30,7 @@
#include "catalog/pg_constraint.h"
#include "catalog/pg_conversion.h"
#include "catalog/pg_conversion_fn.h"
+#include "catalog/pg_custom_plan_provider.h"
#include "catalog/pg_database.h"
#include "catalog/pg_default_acl.h"
#include "catalog/pg_depend.h"
@@ -154,7 +155,8 @@ static const Oid object_classes[MAX_OCLASS] = {
UserMappingRelationId, /* OCLASS_USER_MAPPING */
DefaultAclRelationId, /* OCLASS_DEFACL */
ExtensionRelationId, /* OCLASS_EXTENSION */
- EventTriggerRelationId /* OCLASS_EVENT_TRIGGER */
+ EventTriggerRelationId, /* OCLASS_EVENT_TRIGGER */
+ CustomPlanProviderRelationId, /* OCLASS_CPP */
};
@@ -1249,6 +1251,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemoveEventTriggerById(object->objectId);
break;
+ case OCLASS_CPP:
+ RemoveCustomPlanProviderById(object->objectId);
+ break;
+
default:
elog(ERROR, "unrecognized object class: %u",
object->classId);
@@ -2316,6 +2322,9 @@ getObjectClass(const ObjectAddress *object)
case EventTriggerRelationId:
return OCLASS_EVENT_TRIGGER;
+
+ case CustomPlanProviderRelationId:
+ return OCLASS_CPP;
}
/* shouldn't get here */
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index c7c8f4b..2b0b0e1 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -30,6 +30,7 @@
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_conversion.h"
+#include "catalog/pg_custom_plan_provider.h"
#include "catalog/pg_database.h"
#include "catalog/pg_extension.h"
#include "catalog/pg_foreign_data_wrapper.h"
@@ -152,6 +153,18 @@ static const ObjectPropertyType ObjectProperty[] =
true
},
{
+ CustomPlanProviderRelationId,
+ CustomPlanProviderOidIndexId,
+ CUSTOMPLANPROVIDEROID,
+ CUSTOMPLANPROVIDERNAME,
+ Anum_pg_custom_plan_provider_cppname,
+ InvalidAttrNumber,
+ InvalidAttrNumber,
+ InvalidAttrNumber,
+ -1,
+ false,
+ },
+ {
DatabaseRelationId,
DatabaseOidIndexId,
DATABASEOID,
@@ -529,6 +542,7 @@ get_object_address(ObjectType objtype, List *objname, List *objargs,
case OBJECT_FDW:
case OBJECT_FOREIGN_SERVER:
case OBJECT_EVENT_TRIGGER:
+ case OBJECT_CPP:
address = get_object_address_unqualified(objtype,
objname, missing_ok);
break;
@@ -755,6 +769,9 @@ get_object_address_unqualified(ObjectType objtype,
case OBJECT_EVENT_TRIGGER:
msg = gettext_noop("event trigger name cannot be qualified");
break;
+ case OBJECT_CPP:
+ msg = gettext_noop("custom plan provider name cannot be qualified");
+ break;
default:
elog(ERROR, "unrecognized objtype: %d", (int) objtype);
msg = NULL; /* placate compiler */
@@ -815,6 +832,11 @@ get_object_address_unqualified(ObjectType objtype,
address.objectId = get_event_trigger_oid(name, missing_ok);
address.objectSubId = 0;
break;
+ case OBJECT_CPP:
+ address.classId = CustomPlanProviderRelationId;
+ address.objectId = get_custom_plan_provider_oid(name, missing_ok);
+ address.objectSubId = 0;
+ break;
default:
elog(ERROR, "unrecognized objtype: %d", (int) objtype);
/* placate compiler, which doesn't know elog won't return */
@@ -1295,6 +1317,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
break;
case OBJECT_TSPARSER:
case OBJECT_TSTEMPLATE:
+ case OBJECT_CPP:
/* We treat these object types as being owned by superusers */
if (!superuser_arg(roleid))
ereport(ERROR,
@@ -2166,6 +2189,24 @@ getObjectDescription(const ObjectAddress *object)
break;
}
+ case OCLASS_CPP:
+ {
+ Form_pg_custom_plan_provider cpp_form;
+ HeapTuple tup;
+
+ tup = SearchSysCache1(CUSTOMPLANPROVIDEROID,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR,
+ "cache lookup failed for custom-plan provider %u",
+ object->objectId);
+ cpp_form = (Form_pg_custom_plan_provider) GETSTRUCT(tup);
+ appendStringInfo(&buffer, _("custom plan provider %s"),
+ NameStr(cpp_form->cppname));
+ ReleaseSysCache(tup);
+ break;
+ }
+
default:
appendStringInfo(&buffer, "unrecognized object %u %u %d",
object->classId,
@@ -2577,6 +2618,10 @@ getObjectTypeDescription(const ObjectAddress *object)
appendStringInfoString(&buffer, "event trigger");
break;
+ case OCLASS_CPP:
+ appendStringInfoString(&buffer, "custom plan provider");
+ break;
+
default:
appendStringInfo(&buffer, "unrecognized %u", object->classId);
break;
@@ -3330,6 +3375,24 @@ getObjectIdentity(const ObjectAddress *object)
break;
}
+ case OCLASS_CPP:
+ {
+ HeapTuple tup;
+ Form_pg_custom_plan_provider cpp_form;
+
+ tup = SearchSysCache1(CUSTOMPLANPROVIDEROID,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR,
+ "cache lookup failed for custom-plan provider %u",
+ object->objectId);
+ cpp_form = (Form_pg_custom_plan_provider) GETSTRUCT(tup);
+ appendStringInfoString(&buffer,
+ quote_identifier(NameStr(cpp_form->cppname)));
+ ReleaseSysCache(tup);
+ break;
+ }
+
default:
appendStringInfo(&buffer, "unrecognized object %u %u %d",
object->classId,
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index 22f116b..1e8e6f4 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -14,7 +14,7 @@ include $(top_builddir)/src/Makefile.global
OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o \
collationcmds.o constraint.o conversioncmds.o copy.o createas.o \
- dbcommands.o define.o discard.o dropcmds.o \
+ custom_plan.o dbcommands.o define.o discard.o dropcmds.o \
event_trigger.o explain.o extension.o foreigncmds.o functioncmds.o \
indexcmds.o lockcmds.o matview.o operatorcmds.o opclasscmds.o \
portalcmds.o prepare.o proclang.o \
diff --git a/src/backend/commands/custom_plan.c b/src/backend/commands/custom_plan.c
new file mode 100644
index 0000000..2a81a7b
--- /dev/null
+++ b/src/backend/commands/custom_plan.c
@@ -0,0 +1,188 @@
+/*-------------------------------------------------------------------------
+ *
+ * custom_plan.c
+ * custom plan nodes creation/manipulation commands
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/commands/custom_plan.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_custom_plan_provider.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+#include "commands/defrem.h"
+#include "fmgr.h"
+#include "miscadmin.h"
+#include "parser/parse_func.h"
+#include "utils/builtins.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+/*
+ * utility function to lookup a custom-plan provider by name
+ */
+Oid
+get_custom_plan_provider_oid(const char *cpp_name, bool missing_ok)
+{
+ Oid cpp_oid;
+
+ cpp_oid = GetSysCacheOid1(CUSTOMPLANPROVIDERNAME,
+ CStringGetDatum(cpp_name));
+ if (!OidIsValid(cpp_oid) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("custom-plan provider \"%s\" does not exist",
+ cpp_name)));
+ return cpp_oid;
+}
+
+/*
+ * Drop a custom-plan provider
+ */
+void
+RemoveCustomPlanProviderById(Oid cpp_oid)
+{
+ Relation rel;
+ HeapTuple tuple;
+
+ rel = heap_open(CustomPlanProviderRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCache1(CUSTOMPLANPROVIDEROID,
+ ObjectIdGetDatum(cpp_oid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for custom-plan provider %u",
+ cpp_oid);
+
+ simple_heap_delete(rel, &tuple->t_self);
+
+ ReleaseSysCache(tuple);
+
+ heap_close(rel, RowExclusiveLock);
+}
+
+/*
+ * Create a custom-plan provider
+ */
+Oid
+DefineCustomPlanProvider(CreateCustomPlanProviderStmt *stmt)
+{
+ Relation rel;
+ Oid cpp_oid;
+ Oid cpp_handler = InvalidOid;
+ Datum values[Natts_pg_custom_plan_provider];
+ bool isnull[Natts_pg_custom_plan_provider];
+ HeapTuple tuple;
+ ListCell *cell;
+ ObjectAddress myself;
+ ObjectAddress referenced;
+
+ rel = heap_open(CustomPlanProviderRelationId, RowExclusiveLock);
+
+ /* must be super user */
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to create custom-plan provider \"%s\"",
+ stmt->cpp_name),
+ errhint("Must be superuser to create a custom-plan node.")));
+
+ /* check namespace conflicts */
+ cpp_oid = get_custom_plan_provider_oid(stmt->cpp_name, true);
+ if (OidIsValid(cpp_oid))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("custom-plan provider \"%s\" already exists",
+ stmt->cpp_name)));
+
+ /* check custom-plan class */
+ if (stmt->cpp_class != CUSTOMPLAN_CLASS_SCAN)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unexpected custom plan class specified: %d",
+ (int)stmt->cpp_class)));
+
+ /* parse custom-plan options */
+ foreach (cell, stmt->cpp_options)
+ {
+ DefElem *defel = lfirst(cell);
+
+ Assert(IsA(defel, DefElem));
+
+ if (strcmp(defel->defname, "handler") == 0)
+ {
+ Oid argtypes[1];
+
+ if (OidIsValid(cpp_handler))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting or redundant options")));
+
+ argtypes[0] = INTERNALOID;
+ cpp_handler = LookupFuncName((List *)defel->arg,
+ 1, argtypes, false);
+ if (get_func_rettype(cpp_handler) != VOIDOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("function %s must return type \"void\"",
+ NameListToString((List *) defel->arg))));
+ }
+ else
+ elog(ERROR, "unexpected custom-plan provider option: %s",
+ defel->defname);
+ }
+
+ if (!OidIsValid(cpp_handler))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("HANDLER must be provided")));
+
+ /*
+ * Insert tuple into pg_custom_plan system catalog
+ */
+ memset(values, 0, sizeof(values));
+ memset(isnull, 0, sizeof(isnull));
+ values[Anum_pg_custom_plan_provider_cppname - 1]
+ = DirectFunctionCall1(namein, CStringGetDatum(stmt->cpp_name));
+ values[Anum_pg_custom_plan_provider_cppclass - 1]
+ = stmt->cpp_class;
+ values[Anum_pg_custom_plan_provider_cpphandler - 1]
+ = ObjectIdGetDatum(cpp_handler);
+
+ tuple = heap_form_tuple(RelationGetDescr(rel), values, isnull);
+
+ cpp_oid = simple_heap_insert(rel, tuple);
+ CatalogUpdateIndexes(rel, tuple);
+
+ heap_freetuple(tuple);
+
+ /* record dependencies */
+ myself.classId = CustomPlanProviderRelationId;
+ myself.objectId = cpp_oid;
+ myself.objectSubId = 0;
+
+ referenced.classId = ProcedureRelationId;
+ referenced.objectId = cpp_handler;
+ referenced.objectSubId = 0;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+ /* Post creation hook for new custom-plan provider */
+ InvokeObjectPostCreateHook(CustomPlanProviderRelationId, cpp_oid, 0);
+
+ heap_close(rel, RowExclusiveLock);
+
+ return cpp_oid;
+}
diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c
index e64ad80..c6d4576 100644
--- a/src/backend/commands/dropcmds.c
+++ b/src/backend/commands/dropcmds.c
@@ -408,6 +408,11 @@ does_not_exist_skipping(ObjectType objtype, List *objname, List *objargs)
args = strVal(linitial(objargs));
}
break;
+ case OBJECT_CPP:
+ msg = gettext_noop("custom-plan provider \"%s\" does not exist, skipping");
+ name = NameListToString(objname);
+ break;
+
default:
elog(ERROR, "unexpected object type (%d)", (int) objtype);
break;
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 754264e..76968a2 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -923,6 +923,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_CONSTRAINT:
case OBJECT_COLLATION:
case OBJECT_CONVERSION:
+ case OBJECT_CPP:
case OBJECT_DOMAIN:
case OBJECT_EXTENSION:
case OBJECT_FDW:
@@ -975,6 +976,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_COLLATION:
case OCLASS_CONSTRAINT:
case OCLASS_CONVERSION:
+ case OCLASS_CPP:
case OCLASS_DEFAULT:
case OCLASS_LANGUAGE:
case OCLASS_LARGEOBJECT:
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 781a736..95e8b0e 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -722,6 +722,14 @@ ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
*rels_used = bms_add_member(*rels_used,
((Scan *) plan)->scanrelid);
break;
+ case T_CustomPlan:
+ {
+ CustomPlanState *cps = (CustomPlanState *)planstate;
+
+ if (cps->methods->ExplainCustomPreScanNode)
+ cps->methods->ExplainCustomPreScanNode(cps, rels_used);
+ }
+ break;
case T_ModifyTable:
/* cf ExplainModifyTarget */
*rels_used = bms_add_member(*rels_used,
@@ -848,6 +856,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
const char *sname; /* node type name for non-text output */
const char *strategy = NULL;
const char *operation = NULL;
+ const char *cpp_name = NULL;
int save_indent = es->indent;
bool haschildren;
@@ -936,6 +945,14 @@ ExplainNode(PlanState *planstate, List *ancestors,
case T_ForeignScan:
pname = sname = "Foreign Scan";
break;
+ case T_CustomPlan:
+ sname = "Custom";
+ cpp_name = ((CustomPlan *) plan)->methods->CustomName;
+ if (cpp_name)
+ pname = psprintf("Custom (%s)", cpp_name);
+ else
+ pname = sname;
+ break;
case T_Material:
pname = sname = "Materialize";
break;
@@ -1037,6 +1054,8 @@ ExplainNode(PlanState *planstate, List *ancestors,
ExplainPropertyText("Parent Relationship", relationship, es);
if (plan_name)
ExplainPropertyText("Subplan Name", plan_name, es);
+ if (cpp_name)
+ ExplainPropertyText("Custom", cpp_name, es);
}
switch (nodeTag(plan))
@@ -1084,6 +1103,14 @@ ExplainNode(PlanState *planstate, List *ancestors,
ExplainPropertyText("Index Name", indexname, es);
}
break;
+ case T_CustomPlan:
+ {
+ CustomPlanState *cps = (CustomPlanState *)planstate;
+
+ if (cps->methods->ExplainCustomPlanTargetRel)
+ cps->methods->ExplainCustomPlanTargetRel(cps, es);
+ }
+ break;
case T_ModifyTable:
ExplainModifyTarget((ModifyTable *) plan, es);
break;
@@ -1353,6 +1380,18 @@ ExplainNode(PlanState *planstate, List *ancestors,
planstate, es);
show_foreignscan_info((ForeignScanState *) planstate, es);
break;
+ case T_CustomPlan:
+ {
+ CustomPlanState *cps = (CustomPlanState *) planstate;
+
+ show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
+ if (plan->qual)
+ show_instrumentation_count("Rows Removed by Filter", 1,
+ planstate, es);
+ if (cps->methods->ExplainCustomPlan)
+ cps->methods->ExplainCustomPlan(cps, ancestors, es);
+ }
+ break;
case T_NestLoop:
show_upper_qual(((NestLoop *) plan)->join.joinqual,
"Join Filter", planstate, ancestors, es);
diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile
index 6081b56..af707b0 100644
--- a/src/backend/executor/Makefile
+++ b/src/backend/executor/Makefile
@@ -16,7 +16,7 @@ OBJS = execAmi.o execCurrent.o execGrouping.o execJunk.o execMain.o \
execProcnode.o execQual.o execScan.o execTuples.o \
execUtils.o functions.o instrument.o nodeAppend.o nodeAgg.o \
nodeBitmapAnd.o nodeBitmapOr.o \
- nodeBitmapHeapscan.o nodeBitmapIndexscan.o nodeHash.o \
+ nodeBitmapHeapscan.o nodeBitmapIndexscan.o nodeCustom.o nodeHash.o \
nodeHashjoin.o nodeIndexscan.o nodeIndexonlyscan.o \
nodeLimit.o nodeLockRows.o \
nodeMaterial.o nodeMergeAppend.o nodeMergejoin.o nodeModifyTable.o \
diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c
index 640964c..eaab1df 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -13,6 +13,7 @@
#include "postgres.h"
#include "access/htup_details.h"
+#include "catalog/pg_custom_plan_provider.h"
#include "executor/execdebug.h"
#include "executor/nodeAgg.h"
#include "executor/nodeAppend.h"
@@ -21,6 +22,7 @@
#include "executor/nodeBitmapIndexscan.h"
#include "executor/nodeBitmapOr.h"
#include "executor/nodeCtescan.h"
+#include "executor/nodeCustom.h"
#include "executor/nodeForeignscan.h"
#include "executor/nodeFunctionscan.h"
#include "executor/nodeGroup.h"
@@ -197,6 +199,10 @@ ExecReScan(PlanState *node)
ExecReScanForeignScan((ForeignScanState *) node);
break;
+ case T_CustomPlanState:
+ ExecReScanCustomPlan((CustomPlanState *) node);
+ break;
+
case T_NestLoopState:
ExecReScanNestLoop((NestLoopState *) node);
break;
@@ -291,6 +297,10 @@ ExecMarkPos(PlanState *node)
ExecValuesMarkPos((ValuesScanState *) node);
break;
+ case T_CustomPlanState:
+ ExecCustomMarkPos((CustomPlanState *) node);
+ break;
+
case T_MaterialState:
ExecMaterialMarkPos((MaterialState *) node);
break;
@@ -348,6 +358,10 @@ ExecRestrPos(PlanState *node)
ExecValuesRestrPos((ValuesScanState *) node);
break;
+ case T_CustomPlanState:
+ ExecCustomRestrPos((CustomPlanState *) node);
+ break;
+
case T_MaterialState:
ExecMaterialRestrPos((MaterialState *) node);
break;
@@ -379,9 +393,9 @@ ExecRestrPos(PlanState *node)
* and valuesscan support is actually useless code at present.)
*/
bool
-ExecSupportsMarkRestore(NodeTag plantype)
+ExecSupportsMarkRestore(Path *pathnode)
{
- switch (plantype)
+ switch (pathnode->pathtype)
{
case T_SeqScan:
case T_IndexScan:
@@ -403,6 +417,16 @@ ExecSupportsMarkRestore(NodeTag plantype)
*/
return false;
+ case T_CustomPlan:
+ {
+ CustomPath *cpath = (CustomPath *) pathnode;
+
+ Assert(IsA(cpath, CustomPath));
+ if ((cpath->flags & CPPFLAG_SUPPORT_MARK_RESTORE) != 0)
+ return true;
+ }
+ break;
+
default:
break;
}
@@ -465,6 +489,16 @@ ExecSupportsBackwardScan(Plan *node)
return ExecSupportsBackwardScan(((SubqueryScan *) node)->subplan) &&
TargetListSupportsBackwardScan(node->targetlist);
+ case T_CustomPlan:
+ {
+ CustomPlan *cplan = (CustomPlan *) node;
+
+ if ((cplan->flags & CPPFLAG_SUPPORT_BACKWARD_SCAN) != 0 &&
+ TargetListSupportsBackwardScan(node->targetlist))
+ return false;
+ }
+ return false;
+
case T_Material:
case T_Sort:
/* these don't evaluate tlist */
diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c
index c0189eb..ae77283 100644
--- a/src/backend/executor/execProcnode.c
+++ b/src/backend/executor/execProcnode.c
@@ -85,6 +85,7 @@
#include "executor/nodeBitmapIndexscan.h"
#include "executor/nodeBitmapOr.h"
#include "executor/nodeCtescan.h"
+#include "executor/nodeCustom.h"
#include "executor/nodeForeignscan.h"
#include "executor/nodeFunctionscan.h"
#include "executor/nodeGroup.h"
@@ -244,6 +245,11 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
estate, eflags);
break;
+ case T_CustomPlan:
+ result = (PlanState *) ExecInitCustomPlan((CustomPlan *) node,
+ estate, eflags);
+ break;
+
/*
* join nodes
*/
@@ -442,6 +448,10 @@ ExecProcNode(PlanState *node)
result = ExecForeignScan((ForeignScanState *) node);
break;
+ case T_CustomPlanState:
+ result = ExecCustomPlan((CustomPlanState *) node);
+ break;
+
/*
* join nodes
*/
@@ -558,6 +568,10 @@ MultiExecProcNode(PlanState *node)
result = MultiExecBitmapOr((BitmapOrState *) node);
break;
+ case T_CustomPlanState:
+ result = MultiExecCustomPlan((CustomPlanState *) node);
+ break;
+
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
result = NULL;
@@ -678,6 +692,10 @@ ExecEndNode(PlanState *node)
ExecEndForeignScan((ForeignScanState *) node);
break;
+ case T_CustomPlanState:
+ ExecEndCustomPlan((CustomPlanState *) node);
+ break;
+
/*
* join nodes
*/
diff --git a/src/backend/executor/nodeCustom.c b/src/backend/executor/nodeCustom.c
new file mode 100644
index 0000000..785909e
--- /dev/null
+++ b/src/backend/executor/nodeCustom.c
@@ -0,0 +1,147 @@
+/* ------------------------------------------------------------------------
+ *
+ * nodeCustom.c
+ * Routines to handle execution of custom plan node
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * ------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "executor/executor.h"
+#include "executor/nodeCustom.h"
+#include "nodes/execnodes.h"
+#include "nodes/plannodes.h"
+#include "parser/parsetree.h"
+#include "utils/hsearch.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+
+CustomPlanState *
+ExecInitCustomPlan(CustomPlan *cplan, EState *estate, int eflags)
+{
+ CustomPlanState *cps;
+
+ /* populate a CustomPlanState according to the CustomPlan */
+ cps = (CustomPlanState *)cplan->methods->CreateCustomPlanState(cplan);
+ Assert(IsA(cps, CustomPlanState));
+
+ /* fill up fields of PlanState */
+ cps->ss.ps.plan = &cplan->scan.plan;
+ cps->ss.ps.state = estate;
+
+ /* create expression context for node */
+ ExecAssignExprContext(estate, &cps->ss.ps);
+ cps->ss.ps.ps_TupFromTlist = false;
+
+ /* initialize child expressions */
+ cps->ss.ps.targetlist = (List *)
+ ExecInitExpr((Expr *) cplan->scan.plan.targetlist,
+ (PlanState *) cps);
+ cps->ss.ps.qual = (List *)
+ ExecInitExpr((Expr *) cplan->scan.plan.qual,
+ (PlanState *) cps);
+
+ /* initialization of result tuple slot */
+ ExecInitResultTupleSlot(estate, &cps->ss.ps);
+ ExecAssignResultTypeFromTL(&cps->ss.ps);
+
+ if (cplan->scan.scanrelid > 0)
+ {
+ Relation heap_rel;
+
+ heap_rel = ExecOpenScanRelation(estate, cplan->scan.scanrelid, eflags);
+ cps->ss.ss_currentRelation = heap_rel;
+ cps->ss.ss_currentScanDesc = NULL; /* set by provider on demand */
+ ExecInitScanTupleSlot(estate, &cps->ss);
+ ExecAssignScanType(&cps->ss, RelationGetDescr(heap_rel));
+ ExecAssignScanProjectionInfo(&cps->ss);
+ }
+ else
+ {
+ /*
+ * Elsewhere, custom-plan provider should be responsible to put
+ * appropriate initialization of scan tuple-slot and projection
+ * info.
+ */
+ cps->ss.ss_currentRelation = NULL;
+ cps->ss.ss_currentScanDesc = NULL;
+ cps->ss.ss_ScanTupleSlot = NULL;
+ cps->ss.ps.ps_ProjInfo = NULL;
+ }
+ /*
+ * Then, custom-plan provider can have all the own original
+ * initialization on demand.
+ */
+ cps->methods->BeginCustomPlan(cps, estate, eflags);
+
+ return cps;
+}
+
+TupleTableSlot *
+ExecCustomPlan(CustomPlanState *cpstate)
+{
+ Assert(cpstate->methods->ExecCustomPlan != NULL);
+ return cpstate->methods->ExecCustomPlan(cpstate);
+}
+
+Node *
+MultiExecCustomPlan(CustomPlanState *cpstate)
+{
+ if (!cpstate->methods->MultiExecCustomPlan)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("CustomPlan \"%s\" does not support MultiExec method",
+ cpstate->methods->CustomName)));
+ return cpstate->methods->MultiExecCustomPlan(cpstate);
+}
+
+void
+ExecEndCustomPlan(CustomPlanState *cpstate)
+{
+ Assert(cpstate->methods->EndCustomPlan != NULL);
+ cpstate->methods->EndCustomPlan(cpstate);
+
+ /* Free the exprcontext */
+ ExecFreeExprContext(&cpstate->ss.ps);
+
+ /* Clean out the tuple table */
+ ExecClearTuple(cpstate->ss.ps.ps_ResultTupleSlot);
+ if (cpstate->ss.ss_ScanTupleSlot)
+ ExecClearTuple(cpstate->ss.ss_ScanTupleSlot);
+
+ /* Close the heap relation, if needed */
+ if (cpstate->ss.ss_currentRelation)
+ ExecCloseScanRelation(cpstate->ss.ss_currentRelation);
+}
+
+void
+ExecReScanCustomPlan(CustomPlanState *cpstate)
+{
+ Assert(cpstate->methods->ReScanCustomPlan != NULL);
+ cpstate->methods->ReScanCustomPlan(cpstate);
+}
+
+void
+ExecCustomMarkPos(CustomPlanState *cpstate)
+{
+ if (!cpstate->methods->MarkPosCustomPlan)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("MarkPos is not supported by custom plan provider: %s",
+ cpstate->methods->CustomName)));
+ cpstate->methods->MarkPosCustomPlan(cpstate);
+}
+
+void
+ExecCustomRestrPos(CustomPlanState *cpstate)
+{
+ if (!cpstate->methods->RestrPosCustomPlan)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("RestrPos is not supported by custom plan provider: %s",
+ cpstate->methods->CustomName)));
+ cpstate->methods->RestrPosCustomPlan(cpstate);
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 3088578..14a3e41 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -598,6 +598,22 @@ _copyForeignScan(const ForeignScan *from)
}
/*
+ * _copyCustomPlan
+ */
+static CustomPlan *
+_copyCustomPlan(const CustomPlan *from)
+{
+ CustomPlan *newnode;
+
+ newnode = from->methods->CopyCustomPlan(from);
+ Assert(nodeTag(newnode) == nodeTag(from));
+ CopyScanFields((const Scan *) from, (Scan *) newnode);
+ COPY_SCALAR_FIELD(flags);
+
+ return newnode;
+}
+
+/*
* CopyJoinFields
*
* This function copies the fields of the Join node. It is used by
@@ -3849,6 +3865,19 @@ _copyAlterTSConfigurationStmt(const AlterTSConfigurationStmt *from)
return newnode;
}
+static CreateCustomPlanProviderStmt *
+_copyCreateCustomPlanProviderStmt(const CreateCustomPlanProviderStmt *from)
+{
+ CreateCustomPlanProviderStmt *newnode
+ = makeNode(CreateCustomPlanProviderStmt);
+
+ COPY_STRING_FIELD(cpp_name);
+ COPY_SCALAR_FIELD(cpp_class);
+ COPY_NODE_FIELD(cpp_options);
+
+ return newnode;
+}
+
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
@@ -4012,6 +4041,9 @@ copyObject(const void *from)
case T_ForeignScan:
retval = _copyForeignScan(from);
break;
+ case T_CustomPlan:
+ retval = _copyCustomPlan(from);
+ break;
case T_Join:
retval = _copyJoin(from);
break;
@@ -4561,6 +4593,9 @@ copyObject(const void *from)
case T_AlterTSConfigurationStmt:
retval = _copyAlterTSConfigurationStmt(from);
break;
+ case T_CreateCustomPlanProviderStmt:
+ retval = _copyCreateCustomPlanProviderStmt(from);
+ break;
case T_A_Expr:
retval = _copyAExpr(from);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 1b07db6..bdd626e 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2008,6 +2008,17 @@ _equalAlterTSConfigurationStmt(const AlterTSConfigurationStmt *a,
}
static bool
+_equalCreateCustomPlanProviderStmt(const CreateCustomPlanProviderStmt *a,
+ const CreateCustomPlanProviderStmt *b)
+{
+ COMPARE_STRING_FIELD(cpp_name);
+ COMPARE_SCALAR_FIELD(cpp_class);
+ COMPARE_NODE_FIELD(cpp_options);
+
+ return true;
+}
+
+static bool
_equalAExpr(const A_Expr *a, const A_Expr *b)
{
COMPARE_SCALAR_FIELD(kind);
@@ -3025,6 +3036,9 @@ equal(const void *a, const void *b)
case T_AlterTSConfigurationStmt:
retval = _equalAlterTSConfigurationStmt(a, b);
break;
+ case T_CreateCustomPlanProviderStmt:
+ retval = _equalCreateCustomPlanProviderStmt(a, b);
+ break;
case T_A_Expr:
retval = _equalAExpr(a, b);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 9573a9b..e014a78 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -564,6 +564,17 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
}
static void
+_outCustomPlan(StringInfo str, const CustomPlan *node)
+{
+ WRITE_NODE_TYPE("CUSTOMPLAN");
+
+ _outScanInfo(str, (const Scan *) node);
+ appendStringInfo(str, " :methods");
+ _outToken(str, node->methods->CustomName);
+ node->methods->TextOutCustomPlan(str, node);
+}
+
+static void
_outJoin(StringInfo str, const Join *node)
{
WRITE_NODE_TYPE("JOIN");
@@ -1582,6 +1593,16 @@ _outForeignPath(StringInfo str, const ForeignPath *node)
}
static void
+_outCustomPath(StringInfo str, const CustomPath *node)
+{
+ WRITE_NODE_TYPE("CUSTOMPATH");
+ _outPathInfo(str, (const Path *) node);
+ appendStringInfo(str, " :methods");
+ _outToken(str, node->methods->CustomName);
+ node->methods->TextOutCustomPath(str, node);
+}
+
+static void
_outAppendPath(StringInfo str, const AppendPath *node)
{
WRITE_NODE_TYPE("APPENDPATH");
@@ -2845,6 +2866,9 @@ _outNode(StringInfo str, const void *obj)
case T_ForeignScan:
_outForeignScan(str, obj);
break;
+ case T_CustomPlan:
+ _outCustomPlan(str, obj);
+ break;
case T_Join:
_outJoin(str, obj);
break;
@@ -3053,6 +3077,9 @@ _outNode(StringInfo str, const void *obj)
case T_ForeignPath:
_outForeignPath(str, obj);
break;
+ case T_CustomPath:
+ _outCustomPath(str, obj);
+ break;
case T_AppendPath:
_outAppendPath(str, obj);
break;
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index c81efe9..dbba5ae 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -336,7 +336,7 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
}
break;
case RTE_SUBQUERY:
- /* Subquery --- fully handled during set_rel_size */
+ /* Subquery --- path was added during set_rel_size */
break;
case RTE_FUNCTION:
/* RangeFunction */
@@ -347,12 +347,17 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
set_values_pathlist(root, rel, rte);
break;
case RTE_CTE:
- /* CTE reference --- fully handled during set_rel_size */
+ /* CTE reference --- path was added during set_rel_size */
break;
default:
elog(ERROR, "unexpected rtekind: %d", (int) rel->rtekind);
break;
}
+ /* Also, consider paths by custom-plan providers */
+ call_custom_scan_providers(root, rel, rte);
+
+ /* Select cheapest path */
+ set_cheapest(rel);
}
#ifdef OPTIMIZER_DEBUG
@@ -401,9 +406,6 @@ set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
/* Consider TID scans */
create_tidscan_paths(root, rel);
-
- /* Now find the cheapest of the paths for this rel */
- set_cheapest(rel);
}
/*
@@ -429,9 +431,6 @@ set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
{
/* Call the FDW's GetForeignPaths function to generate path(s) */
rel->fdwroutine->GetForeignPaths(root, rel, rte->relid);
-
- /* Select cheapest path */
- set_cheapest(rel);
}
/*
@@ -1272,9 +1271,6 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Generate appropriate path */
add_path(rel, create_subqueryscan_path(root, rel, pathkeys, required_outer));
-
- /* Select cheapest path (pretty easy in this case...) */
- set_cheapest(rel);
}
/*
@@ -1343,9 +1339,6 @@ set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
/* Generate appropriate path */
add_path(rel, create_functionscan_path(root, rel,
pathkeys, required_outer));
-
- /* Select cheapest path (pretty easy in this case...) */
- set_cheapest(rel);
}
/*
@@ -1366,9 +1359,6 @@ set_values_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
/* Generate appropriate path */
add_path(rel, create_valuesscan_path(root, rel, required_outer));
-
- /* Select cheapest path (pretty easy in this case...) */
- set_cheapest(rel);
}
/*
@@ -1435,9 +1425,6 @@ set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
/* Generate appropriate path */
add_path(rel, create_ctescan_path(root, rel, required_outer));
-
- /* Select cheapest path (pretty easy in this case...) */
- set_cheapest(rel);
}
/*
@@ -1488,9 +1475,6 @@ set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
/* Generate appropriate path */
add_path(rel, create_worktablescan_path(root, rel, required_outer));
-
- /* Select cheapest path (pretty easy in this case...) */
- set_cheapest(rel);
}
/*
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 0cdb790..659daa2 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -2266,7 +2266,7 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path,
* it off does not entitle us to deliver an invalid plan.
*/
else if (innersortkeys == NIL &&
- !ExecSupportsMarkRestore(inner_path->pathtype))
+ !ExecSupportsMarkRestore(inner_path))
path->materialize_inner = true;
/*
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 4b641a2..83efba4 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -77,13 +77,13 @@ static WorkTableScan *create_worktablescan_plan(PlannerInfo *root, Path *best_pa
List *tlist, List *scan_clauses);
static ForeignScan *create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
List *tlist, List *scan_clauses);
+static Plan *create_custom_plan(PlannerInfo *root, CustomPath *best_path);
static NestLoop *create_nestloop_plan(PlannerInfo *root, NestPath *best_path,
Plan *outer_plan, Plan *inner_plan);
static MergeJoin *create_mergejoin_plan(PlannerInfo *root, MergePath *best_path,
Plan *outer_plan, Plan *inner_plan);
static HashJoin *create_hashjoin_plan(PlannerInfo *root, HashPath *best_path,
Plan *outer_plan, Plan *inner_plan);
-static Node *replace_nestloop_params(PlannerInfo *root, Node *expr);
static Node *replace_nestloop_params_mutator(Node *node, PlannerInfo *root);
static void process_subquery_nestloop_params(PlannerInfo *root,
List *subplan_params);
@@ -261,6 +261,9 @@ create_plan_recurse(PlannerInfo *root, Path *best_path)
plan = create_unique_plan(root,
(UniquePath *) best_path);
break;
+ case T_CustomPlan:
+ plan = create_custom_plan(root, (CustomPath *) best_path);
+ break;
default:
elog(ERROR, "unrecognized node type: %d",
(int) best_path->pathtype);
@@ -1072,6 +1075,96 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path)
return plan;
}
+/*
+ * create_custom_plan
+ *
+ * Returns a custom-scan plan for the base relation scanned by 'best_path'
+ * with restriction clauses 'scan_clauses' and targetlist 'tlist'.
+ */
+static Plan *
+create_custom_plan(PlannerInfo *root, CustomPath *best_path)
+{
+ CustomPlan *custom_plan;
+ RelOptInfo *rel;
+ List *tlist = NIL;
+ List *clauses = NIL;
+
+ /*
+ * Create a custom-plan object delivered from CustomPlan type,
+ * according to the supplied CustomPath
+ */
+ Assert(best_path->path.pathtype == T_CustomPlan);
+ custom_plan = (CustomPlan *)
+ best_path->methods->CreateCustomPlan(root, best_path);
+ Assert(IsA(custom_plan, CustomPlan));
+
+ rel = best_path->path.parent;
+ if (rel)
+ {
+ if (rel->reloptkind == RELOPT_BASEREL)
+ {
+ Assert(rel->relid > 0);
+ custom_plan->scan.scanrelid = rel->relid;
+
+ /*
+ * For table scans, rather than using the relation targetlist
+ * (which is only those Vars actually needed by the query),
+ * we prefer to generate a tlist containing all Vars in order.
+ * This will allow the executor to optimize away projection of
+ * the table tuples, if possible.
+ */
+ if (use_physical_tlist(root, rel))
+ tlist = build_physical_tlist(root, rel);
+ }
+ /* elsewhere, we generate a tlist from the relation targetlist */
+ if (tlist == NIL)
+ tlist = build_path_tlist(root, &best_path->path);
+
+ /*
+ * Extract the relevant restriction clauses from the parent relation.
+ * The executor must apply all these restrictions during the scan,
+ * except for pseudoconstants which we'll take care of below.
+ */
+ clauses = rel->baserestrictinfo;
+
+ /*
+ * If this is a parameterized scan, we also need to enforce all
+ * the join clauses available from the outer relation(s).
+ */
+ if (best_path->path.param_info)
+ clauses = list_concat(list_copy(clauses),
+ best_path->path.param_info->ppi_clauses);
+
+ /* Sort clauses into best execution order */
+ clauses = order_qual_clauses(root, clauses);
+
+ /*
+ * Replace outer-relation variables with nestloop params.
+ * Note that any other clauses which is managed by extension
+ * itself has to be handled in InitCustomPlan() method, as
+ * built-in code doing.
+ */
+ if (best_path->path.param_info)
+ clauses = (List *)replace_nestloop_params(root, (Node *)clauses);
+ }
+ /*
+ * Copy cost data from Path to Plan; no need to make custom-plan
+ * providers do this
+ */
+ copy_path_costsize((Plan *)custom_plan, (Path *)best_path);
+
+ /*
+ * Let the custom-plan provider perform its final initialization
+ * of this CustomPlan (to be an inherited type, actually) node
+ * according to its own necessity.
+ * Note that custom-plan provider may/can replace (or stack another
+ * one on) its own custom-plan node on demand, for example, to add
+ * Result node to handle pseudo constant using create_gating_plan().
+ */
+ return custom_plan->methods->InitCustomPlan(custom_plan,
+ root, best_path,
+ tlist, clauses);
+}
/*****************************************************************************
*
@@ -2540,7 +2633,7 @@ create_hashjoin_plan(PlannerInfo *root,
* root->curOuterRels are replaced by Params, and entries are added to
* root->curOuterParams if not already present.
*/
-static Node *
+Node *
replace_nestloop_params(PlannerInfo *root, Node *expr)
{
/* No setup needed for tree walk, so away we go */
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 4d717df..c416859 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -94,7 +94,6 @@ static Plan *set_subqueryscan_references(PlannerInfo *root,
SubqueryScan *plan,
int rtoffset);
static bool trivial_subqueryscan(SubqueryScan *plan);
-static Node *fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset);
static Node *fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context);
static bool fix_scan_expr_walker(Node *node, fix_scan_expr_context *context);
static void set_join_references(PlannerInfo *root, Join *join, int rtoffset);
@@ -579,6 +578,14 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
}
break;
+ case T_CustomPlan:
+ {
+ CustomPlan *cplan = (CustomPlan *) plan;
+
+ cplan->methods->SetCustomPlanRef(root, cplan, rtoffset);
+ }
+ break;
+
case T_NestLoop:
case T_MergeJoin:
case T_HashJoin:
@@ -1158,7 +1165,7 @@ fix_param_node(PlannerInfo *root, Param *p)
* looking up operator opcode info for OpExpr and related nodes,
* and adding OIDs from regclass Const nodes into root->glob->relationOids.
*/
-static Node *
+Node *
fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset)
{
fix_scan_expr_context context;
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 3e7dc85..9833073 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -2283,6 +2283,30 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
context.paramids = bms_add_members(context.paramids, scan_params);
break;
+ case T_CustomPlan:
+ {
+ CustomPlan *cplan = (CustomPlan *) plan;
+
+ /*
+ * If this custom-plan scab a particular relation, we
+ * adjust paramids as other scan derivered node.
+ */
+ if (cplan->scan.scanrelid > 0)
+ context.paramids = bms_add_members(context.paramids,
+ scan_params);
+ /*
+ * custom plan provider is responsible to apply
+ * finalize_primnode() on the expression node of its
+ * private fields, but no need to apply tlist and
+ * qual of Plan node (already done above).
+ */
+ if (cplan->methods->FinalizeCustomPlan)
+ cplan->methods->FinalizeCustomPlan(root, cplan,
+ finalize_primnode,
+ (void *)&context);
+ }
+ break;
+
case T_ModifyTable:
{
ModifyTable *mtplan = (ModifyTable *) plan;
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index d129f8d..e946fc7 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -16,6 +16,10 @@
#include
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "catalog/pg_custom_plan_provider.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
@@ -25,8 +29,11 @@
#include "optimizer/restrictinfo.h"
#include "optimizer/tlist.h"
#include "parser/parsetree.h"
+#include "utils/inval.h"
#include "utils/lsyscache.h"
+#include "utils/memutils.h"
#include "utils/selfuncs.h"
+#include "utils/syscache.h"
typedef enum
@@ -2078,3 +2085,106 @@ reparameterize_path(PlannerInfo *root, Path *path,
}
return NULL;
}
+
+/*****************************************************************************
+ * creation of custom-plan paths
+ *****************************************************************************/
+static List *custom_scan_callchain = NIL;
+static bool custom_plan_callchain_is_ready = false;
+static MemoryContext custom_plan_memcxt = NULL;
+
+static void
+invalidate_custom_plan_callchain(Datum arg, int cacheid, uint32 hashvalue)
+{
+ MemoryContextReset(custom_plan_memcxt);
+ custom_plan_callchain_is_ready = false;
+ custom_scan_callchain = NIL;
+}
+
+static void
+setup_custom_plan_callchain(void)
+{
+ Relation rel;
+ SysScanDesc scan;
+ HeapTuple tuple;
+ MemoryContext oldcxt;
+
+ custom_scan_callchain = NIL;
+
+ rel = heap_open(CustomPlanProviderRelationId, AccessShareLock);
+
+ /* full scan on the pg_custom_plan once */
+ scan = systable_beginscan(rel, InvalidOid, false, NULL, 0, NULL);
+
+ oldcxt = MemoryContextSwitchTo(custom_plan_memcxt);
+ while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+ {
+ Form_pg_custom_plan_provider cppForm
+ = (Form_pg_custom_plan_provider) GETSTRUCT(tuple);
+
+ if (cppForm->cppclass == CUSTOMPLAN_CLASS_SCAN)
+ {
+ custom_scan_callchain = lappend_oid(custom_scan_callchain,
+ cppForm->cpphandler);
+ }
+ else
+ elog(LOG, "Bug? custom-plan provider \"%s\" has unknown class: %c",
+ NameStr(cppForm->cppname), cppForm->cppclass);
+ }
+ MemoryContextSwitchTo(oldcxt);
+ systable_endscan(scan);
+
+ heap_close(rel, AccessShareLock);
+
+ custom_plan_callchain_is_ready = true;
+}
+
+static void
+init_custom_plan_callchain(void)
+{
+ /* memory context to keep callchain for custom-plans */
+ custom_plan_memcxt = AllocSetContextCreate(CacheMemoryContext,
+ "custom plan memory context",
+ ALLOCSET_DEFAULT_MINSIZE,
+ ALLOCSET_DEFAULT_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
+
+ /* flush cached callchain on catalog updates */
+ CacheRegisterSyscacheCallback(CUSTOMPLANPROVIDEROID,
+ invalidate_custom_plan_callchain,
+ (Datum) 0);
+ /* also, initial setting up */
+ setup_custom_plan_callchain();
+}
+
+/*
+ * call_custom_scan_providers
+ *
+ * A callchain on relation scan. custom-plan provider can add alternative
+ * scan paths derived from CustomPath class.
+ */
+void
+call_custom_scan_providers(PlannerInfo *root,
+ RelOptInfo *baserel,
+ RangeTblEntry *rte)
+{
+ customScanArg sarg;
+ ListCell *cell;
+
+ if (!custom_plan_memcxt)
+ init_custom_plan_callchain();
+ else if (!custom_plan_callchain_is_ready)
+ setup_custom_plan_callchain();
+
+ Assert(custom_plan_callchain_is_ready);
+ sarg.cpp_class = CUSTOMPLAN_CLASS_SCAN;
+ sarg.root = root;
+ sarg.baserel = baserel;
+ sarg.rte = rte;
+
+ foreach (cell, custom_scan_callchain)
+ {
+ (void) OidFunctionCall1(lfirst_oid(cell),
+ PointerGetDatum(&sarg));
+ }
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index a113809..c8ea278 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -51,6 +51,7 @@
#include "catalog/index.h"
#include "catalog/namespace.h"
+#include "catalog/pg_custom_plan_provider.h"
#include "catalog/pg_trigger.h"
#include "commands/defrem.h"
#include "commands/trigger.h"
@@ -259,6 +260,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DropOwnedStmt ReassignOwnedStmt
AlterTSConfigurationStmt AlterTSDictionaryStmt
CreateMatViewStmt RefreshMatViewStmt
+ CreateCustomPlanProviderStmt DropCustomPlanProviderStmt
%type select_no_parens select_with_parens select_clause
simple_select values_clause
@@ -514,6 +516,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type opt_existing_window_name
%type opt_if_not_exists
+%type cpp_class
+%type cpp_option
+%type opt_cpp_options cpp_options
+
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
* They must be listed first so that their numeric codes do not depend on
@@ -549,7 +555,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE
CROSS CSV CURRENT_P
CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA
- CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE
+ CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CUSTOM CYCLE
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DESC
@@ -588,8 +594,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
OBJECT_P OF OFF OFFSET OIDS ON ONLY OPERATOR OPTION OPTIONS OR
ORDER ORDINALITY OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER
- PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POSITION
- PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
+ PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLAN PLANS POSITION
+ PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY PROVIDER
PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROGRAM
QUOTE
@@ -599,8 +605,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK
ROW ROWS RULE
- SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
- SERIALIZABLE SERVER SESSION SESSION_USER SET SETOF SHARE
+ SAVEPOINT SCAN SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE
+ SEQUENCES SERIALIZABLE SERVER SESSION SESSION_USER SET SETOF SHARE
SHOW SIMILAR SIMPLE SMALLINT SNAPSHOT SOME STABLE STANDALONE_P START
STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBSTRING
SYMMETRIC SYSID SYSTEM_P
@@ -762,6 +768,7 @@ stmt :
| CreateAssertStmt
| CreateCastStmt
| CreateConversionStmt
+ | CreateCustomPlanProviderStmt
| CreateDomainStmt
| CreateExtensionStmt
| CreateFdwStmt
@@ -792,6 +799,7 @@ stmt :
| DoStmt
| DropAssertStmt
| DropCastStmt
+ | DropCustomPlanProviderStmt
| DropFdwStmt
| DropForeignServerStmt
| DropGroupStmt
@@ -8705,6 +8713,78 @@ CreateConversionStmt:
}
;
+/****************************************************************************
+ *
+ * QUERY:
+ * CREATE CUSTOM PLAN PROVIDER name FOR
+ *
+ ****************************************************************************/
+
+CreateCustomPlanProviderStmt:
+ CREATE CUSTOM PLAN PROVIDER name FOR cpp_class opt_cpp_options
+ {
+ CreateCustomPlanProviderStmt *n
+ = makeNode(CreateCustomPlanProviderStmt);
+ n->cpp_name = $5;
+ n->cpp_class = $7;
+ n->cpp_options = $8;
+ $$ = (Node *) n;
+ }
+ ;
+
+cpp_class:
+ SCAN { $$ = CUSTOMPLAN_CLASS_SCAN; }
+ ;
+
+cpp_option:
+ HANDLER handler_name
+ {
+ $$ = makeDefElem("handler", (Node *)$2);
+ }
+ ;
+
+cpp_options:
+ cpp_option { $$ = list_make1($1); }
+ | cpp_options cpp_option { $$ = lappend($1, $2); }
+ ;
+
+opt_cpp_options:
+ cpp_options { $$ = $1; }
+ | /* empty */ { $$ = NIL; }
+ ;
+
+/****************************************************************************
+ *
+ * QUERY:
+ * DROP CUSTOM PLAN PROVIDER name
+ *
+ ****************************************************************************/
+
+DropCustomPlanProviderStmt:
+ DROP CUSTOM PLAN PROVIDER name opt_drop_behavior
+ {
+ DropStmt *n = makeNode(DropStmt);
+ n->removeType = OBJECT_CPP;
+ n->objects = list_make1(list_make1(makeString($5)));
+ n->arguments = NIL;
+ n->missing_ok = false;
+ n->behavior = $6;
+ n->concurrent = false;
+ $$ = (Node *) n;
+ }
+ | DROP CUSTOM PLAN PROVIDER IF_P EXISTS name opt_drop_behavior
+ {
+ DropStmt *n = makeNode(DropStmt);
+ n->removeType = OBJECT_CPP;
+ n->objects = list_make1(list_make1(makeString($7)));
+ n->arguments = NIL;
+ n->missing_ok = true;
+ n->behavior = $8;
+ n->concurrent = false;
+ $$ = (Node *) n;
+ }
+ ;
+
/*****************************************************************************
*
* QUERY:
@@ -12914,6 +12994,7 @@ unreserved_keyword:
| CSV
| CURRENT_P
| CURSOR
+ | CUSTOM
| CYCLE
| DATA_P
| DATABASE
@@ -13025,6 +13106,7 @@ unreserved_keyword:
| PARTITION
| PASSING
| PASSWORD
+ | PLAN
| PLANS
| PRECEDING
| PREPARE
@@ -13035,6 +13117,7 @@ unreserved_keyword:
| PROCEDURAL
| PROCEDURE
| PROGRAM
+ | PROVIDER
| QUOTE
| RANGE
| READ
@@ -13060,6 +13143,7 @@ unreserved_keyword:
| ROWS
| RULE
| SAVEPOINT
+ | SCAN
| SCHEMA
| SCROLL
| SEARCH
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 07e0b98..cbfd3cf 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -202,6 +202,7 @@ check_xact_readonly(Node *parsetree)
case T_AlterTableSpaceOptionsStmt:
case T_AlterTableSpaceMoveStmt:
case T_CreateForeignTableStmt:
+ case T_CreateCustomPlanProviderStmt:
case T_ImportForeignSchemaStmt:
case T_SecLabelStmt:
PreventCommandIfReadOnly(CreateCommandTag(parsetree));
@@ -684,6 +685,14 @@ standard_ProcessUtility(Node *parsetree,
AlterEventTrigger((AlterEventTrigStmt *) parsetree);
break;
+ case T_CreateCustomPlanProviderStmt:
+ {
+ CreateCustomPlanProviderStmt *cpp_stmt
+ = (CreateCustomPlanProviderStmt *)parsetree;
+ DefineCustomPlanProvider(cpp_stmt);
+ }
+ break;
+
/*
* ******************************** ROLE statements ****
*/
@@ -1949,6 +1958,9 @@ CreateCommandTag(Node *parsetree)
case OBJECT_OPFAMILY:
tag = "DROP OPERATOR FAMILY";
break;
+ case OBJECT_CPP:
+ tag = "DROP CUSTOM PLAN PROVIDER";
+ break;
default:
tag = "???";
}
@@ -2216,6 +2228,10 @@ CreateCommandTag(Node *parsetree)
tag = "ALTER EVENT TRIGGER";
break;
+ case T_CreateCustomPlanProviderStmt:
+ tag = "CREATE CUSTOM PLAN PROVIDER";
+ break;
+
case T_CreatePLangStmt:
tag = "CREATE LANGUAGE";
break;
@@ -2840,6 +2856,10 @@ GetCommandLogLevel(Node *parsetree)
lev = LOGSTMT_DDL;
break;
+ case T_CreateCustomPlanProviderStmt:
+ lev = LOGSTMT_DDL;
+ break;
+
/* already-planned queries */
case T_PlannedStmt:
{
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 0781ac8..abe27e2 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -5481,6 +5481,29 @@ get_utility_query_def(Query *query, deparse_context *context)
}
}
+/*
+ * GetSpecialCustomVar
+ *
+ * This routine provides a way to resolve where the supplied varnode
+ * actually references, using GetSpecialCustomVar method, in case when
+ * custom-plan provider replaced a varno in expression tree by special
+ * varno.
+ */
+static Node *
+GetSpecialCustomVar(PlanState *ps, Var *varnode, PlanState **child_ps)
+{
+ CustomPlanState *cps = (CustomPlanState *) ps;
+
+ Assert(IsA(ps, CustomPlanState));
+ Assert(IS_SPECIAL_VARNO(varnode->varno));
+
+ if (!cps->methods->GetSpecialCustomVar)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("%s does not support special varno reference",
+ cps->methods->CustomName)));
+ return (Node *)cps->methods->GetSpecialCustomVar(cps, varnode, child_ps);
+}
/*
* Display a Var appropriately.
@@ -5510,6 +5533,8 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
int netlevelsup;
deparse_namespace *dpns;
deparse_columns *colinfo;
+ PlanState *child_ps = NULL;
+ Node *expr;
char *refname;
char *attname;
@@ -5534,6 +5559,29 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
colinfo = deparse_columns_fetch(var->varno, dpns);
attnum = var->varattno;
}
+ else if (IS_SPECIAL_VARNO(var->varno) &&
+ IsA(dpns->planstate, CustomPlanState) &&
+ (expr = GetSpecialCustomVar(dpns->planstate, var,
+ &child_ps)) != NULL)
+ {
+ deparse_namespace save_dpns;
+
+ if (child_ps)
+ push_child_plan(dpns, child_ps, &save_dpns);
+ /*
+ * Force parentheses because our caller probably assumed a Var is a
+ * simple expression.
+ */
+ if (!IsA(expr, Var))
+ appendStringInfoChar(buf, '(');
+ get_rule_expr((Node *) expr, context, true);
+ if (!IsA(expr, Var))
+ appendStringInfoChar(buf, ')');
+
+ if (child_ps)
+ pop_child_plan(dpns, &save_dpns);
+ return NULL;
+ }
else if (var->varno == OUTER_VAR && dpns->outer_tlist)
{
TargetEntry *tle;
@@ -5748,6 +5796,7 @@ get_name_for_var_field(Var *var, int fieldno,
AttrNumber attnum;
int netlevelsup;
deparse_namespace *dpns;
+ PlanState *child_ps = NULL;
TupleDesc tupleDesc;
Node *expr;
@@ -5822,6 +5871,30 @@ get_name_for_var_field(Var *var, int fieldno,
rte = rt_fetch(var->varno, dpns->rtable);
attnum = var->varattno;
}
+ else if (IS_SPECIAL_VARNO(var->varno) &&
+ IsA(dpns->planstate, CustomPlanState) &&
+ (expr = GetSpecialCustomVar(dpns->planstate, var,
+ &child_ps)) != NULL)
+ {
+ StringInfo saved = context->buf;
+ StringInfoData temp;
+ deparse_namespace save_dpns;
+
+ initStringInfo(&temp);
+ context->buf = &temp;
+
+ if (child_ps)
+ push_child_plan(dpns, child_ps, &save_dpns);
+ if (!IsA(expr, Var))
+ appendStringInfoChar(context->buf, '(');
+ get_rule_expr((Node *) expr, context, true);
+ if (!IsA(expr, Var))
+ appendStringInfoChar(context->buf, ')');
+ if (child_ps)
+ pop_child_plan(dpns, &save_dpns);
+ context->buf = saved;
+ return temp.data;
+ }
else if (var->varno == OUTER_VAR && dpns->outer_tlist)
{
TargetEntry *tle;
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 94d951c..1619de7 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -32,6 +32,7 @@
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_conversion.h"
+#include "catalog/pg_custom_plan_provider.h"
#include "catalog/pg_database.h"
#include "catalog/pg_db_role_setting.h"
#include "catalog/pg_default_acl.h"
@@ -345,6 +346,28 @@ static const struct cachedesc cacheinfo[] = {
},
8
},
+ {CustomPlanProviderRelationId, /* CUSTOMPLANPROVIDEROID */
+ CustomPlanProviderOidIndexId,
+ 1,
+ {
+ ObjectIdAttributeNumber,
+ 0,
+ 0,
+ 0
+ },
+ 32
+ },
+ {CustomPlanProviderRelationId, /* CUSTOMPLANPROVIDERNAME */
+ CustomPlanProviderNameIndexId,
+ 1,
+ {
+ Anum_pg_custom_plan_provider_cppname,
+ 0,
+ 0,
+ 0,
+ },
+ 32
+ },
{DatabaseRelationId, /* DATABASEOID */
DatabaseOidIndexId,
1,
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 8ed2592..4a7186a 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -147,6 +147,7 @@ typedef enum ObjectClass
OCLASS_DEFACL, /* pg_default_acl */
OCLASS_EXTENSION, /* pg_extension */
OCLASS_EVENT_TRIGGER, /* pg_event_trigger */
+ OCLASS_CPP, /* pg_custom_plan_provider */
MAX_OCLASS /* MUST BE LAST */
} ObjectClass;
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 59576fd..b926e15 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -299,6 +299,12 @@ DECLARE_UNIQUE_INDEX(pg_extension_name_index, 3081, on pg_extension using btree(
DECLARE_UNIQUE_INDEX(pg_range_rngtypid_index, 3542, on pg_range using btree(rngtypid oid_ops));
#define RangeTypidIndexId 3542
+DECLARE_UNIQUE_INDEX(pg_custom_plan_provider_oid_index, 3563, on pg_custom_plan_provider using btree(oid oid_ops));
+#define CustomPlanProviderOidIndexId 3563
+
+DECLARE_UNIQUE_INDEX(pg_custom_plan_provider_name_index, 3564, on pg_custom_plan_provider using btree(cppname name_ops));
+#define CustomPlanProviderNameIndexId 3564
+
/* last step of initialization script: build the indexes declared above */
BUILD_INDICES
diff --git a/src/include/catalog/pg_custom_plan_provider.h b/src/include/catalog/pg_custom_plan_provider.h
new file mode 100644
index 0000000..32d8d19
--- /dev/null
+++ b/src/include/catalog/pg_custom_plan_provider.h
@@ -0,0 +1,56 @@
+/* -------------------------------------------------------------------------
+ *
+ * pg_custom_plan_provider.h
+ * definition of the system "custom plan provider" relation
+ * (pg_custom_plan_provider)
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * -------------------------------------------------------------------------
+ */
+#ifndef PG_CUSTOM_PLAN_PROVIDER_H
+#define PG_CUSTOM_PLAN_PROVIDER_H
+
+#include "catalog/genbki.h"
+
+/* ----------------
+ * pg_custom_plan_provider definition. cpp turns this into
+ * typedef struct FormData_pg_custom_plan_provider
+ * ----------------
+ */
+#define CustomPlanProviderRelationId 3562
+
+CATALOG(pg_custom_plan_provider,3562)
+{
+ NameData cppname; /* name of custom-plan provider */
+ char cppclass; /* class of custom-plan */
+ Oid cpphandler; /* function of custom-plan provider */
+} FormData_pg_custom_plan_provider;
+
+/* ----------------
+ * Form_pg_custom_plan_provider corresponds to a pointer to a tuple
+ * with the format of pg_custom_plan_provider relation.
+ * ----------------
+ */
+typedef FormData_pg_custom_plan_provider *Form_pg_custom_plan_provider;
+
+/* ----------------
+ * compiler constants for pg_custom_plan_provider
+ * ----------------
+ */
+#define Natts_pg_custom_plan_provider 3
+#define Anum_pg_custom_plan_provider_cppname 1
+#define Anum_pg_custom_plan_provider_cppclass 2
+#define Anum_pg_custom_plan_provider_cpphandler 3
+
+/* definition of cppclass */
+#define CUSTOMPLAN_CLASS_SCAN 's'
+
+/*
+ * definition of flags in CustomPath/Plan/PlanState node
+ */
+#define CPPFLAG_SUPPORT_MARK_RESTORE 0x0001
+#define CPPFLAG_SUPPORT_BACKWARD_SCAN 0x0002
+
+#endif /* PG_CUSTOM_PLAN_PROVIDER_H */
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index f8b4a65..fc798a2 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -166,10 +166,13 @@ DESCR("less than");
#define TIDLessOperator 2799
DATA(insert OID = 2800 ( ">" PGNSP PGUID b f f 27 27 16 2799 2801 tidgt scalargtsel scalargtjoinsel ));
DESCR("greater than");
+#define TIDGreaterOperator 2800
DATA(insert OID = 2801 ( "<=" PGNSP PGUID b f f 27 27 16 2802 2800 tidle scalarltsel scalarltjoinsel ));
DESCR("less than or equal");
+#define TIDLessEqualOperator 2801
DATA(insert OID = 2802 ( ">=" PGNSP PGUID b f f 27 27 16 2801 2799 tidge scalargtsel scalargtjoinsel ));
DESCR("greater than or equal");
+#define TIDGreaterEqualOperator 2802
DATA(insert OID = 410 ( "=" PGNSP PGUID b t t 20 20 16 410 411 int8eq eqsel eqjoinsel ));
DESCR("equal");
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 0ebdbc1..b0f3f0c 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -130,6 +130,11 @@ extern Datum transformGenericOptions(Oid catalogId,
List *options,
Oid fdwvalidator);
+/* commands/custom_plan.c */
+extern Oid get_custom_plan_provider_oid(const char *cpp_name, bool missing_ok);
+extern void RemoveCustomPlanProviderById(Oid cust_oid);
+extern Oid DefineCustomPlanProvider(CreateCustomPlanProviderStmt *stmt);
+
/* support routines in commands/define.c */
extern char *defGetString(DefElem *def);
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 239aff3..b5d05b0 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -102,7 +102,7 @@ extern PGDLLIMPORT ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook;
extern void ExecReScan(PlanState *node);
extern void ExecMarkPos(PlanState *node);
extern void ExecRestrPos(PlanState *node);
-extern bool ExecSupportsMarkRestore(NodeTag plantype);
+extern bool ExecSupportsMarkRestore(Path *pathnode);
extern bool ExecSupportsBackwardScan(Plan *node);
extern bool ExecMaterializesOutput(NodeTag plantype);
diff --git a/src/include/executor/nodeCustom.h b/src/include/executor/nodeCustom.h
new file mode 100644
index 0000000..abe1e94
--- /dev/null
+++ b/src/include/executor/nodeCustom.h
@@ -0,0 +1,30 @@
+/* ------------------------------------------------------------------------
+ *
+ * nodeCustom.h
+ *
+ * prototypes for CustomPlan nodes
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * ------------------------------------------------------------------------
+ */
+#ifndef NODECUSTOM_H
+#define NODECUSTOM_H
+#include "nodes/plannodes.h"
+#include "nodes/execnodes.h"
+
+/*
+ * General executor code
+ */
+extern CustomPlanState *ExecInitCustomPlan(CustomPlan *cplan,
+ EState *estate, int eflags);
+extern TupleTableSlot *ExecCustomPlan(CustomPlanState *node);
+extern Node *MultiExecCustomPlan(CustomPlanState *node);
+extern void ExecEndCustomPlan(CustomPlanState *node);
+
+extern void ExecReScanCustomPlan(CustomPlanState *node);
+extern void ExecCustomMarkPos(CustomPlanState *node);
+extern void ExecCustomRestrPos(CustomPlanState *node);
+
+#endif /* NODECUSTOM_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index b271f21..8838a6d 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -19,6 +19,7 @@
#include "executor/instrument.h"
#include "nodes/params.h"
#include "nodes/plannodes.h"
+#include "nodes/relation.h"
#include "utils/reltrigger.h"
#include "utils/sortsupport.h"
#include "utils/tuplestore.h"
@@ -1504,6 +1505,49 @@ typedef struct ForeignScanState
void *fdw_state; /* foreign-data wrapper can keep state here */
} ForeignScanState;
+/* ----------------
+ * CustomPlanState information
+ *
+ * CustomPlan nodes are used to execute custom code within executor.
+ * ----------------
+ */
+struct CustomExecMethods;
+struct ExplainState; /* to avoid to include explain.h here */
+
+typedef struct CustomPlanState
+{
+ ScanState ss;
+ int flags; /* CPPFLAG_* defined in pg_custom_plan_provider.h */
+ const struct CustomExecMethods *methods;
+} CustomPlanState;
+
+typedef struct CustomExecMethods
+{
+ const char *CustomName;
+
+ /* EXECUTOR methods */
+ void (*BeginCustomPlan)(CustomPlanState *node,
+ EState *estate,
+ int eflags);
+ TupleTableSlot *(*ExecCustomPlan)(CustomPlanState *node);
+ Node *(*MultiExecCustomPlan)(CustomPlanState *node);
+ void (*EndCustomPlan)(CustomPlanState *node);
+ void (*ReScanCustomPlan)(CustomPlanState *node);
+ void (*MarkPosCustomPlan)(CustomPlanState *node);
+ void (*RestrPosCustomPlan)(CustomPlanState *node);
+
+ /* EXPLAIN support */
+ void (*ExplainCustomPlanTargetRel)(CustomPlanState *node,
+ struct ExplainState *es);
+ void (*ExplainCustomPlan)(CustomPlanState *node,
+ List *ancestors,
+ struct ExplainState *es);
+ void (*ExplainCustomPreScanNode)(CustomPlanState *node,
+ Bitmapset **rels_used);
+ Node *(*GetSpecialCustomVar)(CustomPlanState *node, Var *varnode,
+ PlanState **child_ps);
+} CustomExecMethods;
+
/* ----------------------------------------------------------------
* Join State Information
* ----------------------------------------------------------------
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 067c768..b9672b2 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -62,6 +62,7 @@ typedef enum NodeTag
T_CteScan,
T_WorkTableScan,
T_ForeignScan,
+ T_CustomPlan,
T_Join,
T_NestLoop,
T_MergeJoin,
@@ -107,6 +108,7 @@ typedef enum NodeTag
T_CteScanState,
T_WorkTableScanState,
T_ForeignScanState,
+ T_CustomPlanState,
T_JoinState,
T_NestLoopState,
T_MergeJoinState,
@@ -224,6 +226,7 @@ typedef enum NodeTag
T_HashPath,
T_TidPath,
T_ForeignPath,
+ T_CustomPath,
T_AppendPath,
T_MergeAppendPath,
T_ResultPath,
@@ -366,6 +369,7 @@ typedef enum NodeTag
T_RefreshMatViewStmt,
T_ReplicaIdentityStmt,
T_AlterSystemStmt,
+ T_CreateCustomPlanProviderStmt,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 8364bef..1f98cce 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1209,6 +1209,7 @@ typedef enum ObjectType
OBJECT_CONSTRAINT,
OBJECT_COLLATION,
OBJECT_CONVERSION,
+ OBJECT_CPP,
OBJECT_DATABASE,
OBJECT_DOMAIN,
OBJECT_EVENT_TRIGGER,
@@ -2073,6 +2074,18 @@ typedef struct AlterOpFamilyStmt
} AlterOpFamilyStmt;
/* ----------------------
+ * Create Custom Plan Provider Statement
+ * ----------------------
+ */
+typedef struct CreateCustomPlanProviderStmt
+{
+ NodeTag type;
+ char *cpp_name; /* name of custom-plan provider */
+ char cpp_class; /* class of custom-plan provides */
+ List *cpp_options; /* generic options for provider */
+} CreateCustomPlanProviderStmt;
+
+/* ----------------------
* Drop Table|Sequence|View|Index|Type|Domain|Conversion|Schema Statement
* ----------------------
*/
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 3b9c683..75d4c23 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -15,6 +15,7 @@
#define PLANNODES_H
#include "access/sdir.h"
+#include "lib/stringinfo.h"
#include "nodes/bitmapset.h"
#include "nodes/primnodes.h"
@@ -479,6 +480,42 @@ typedef struct ForeignScan
bool fsSystemCol; /* true if any "system column" is needed */
} ForeignScan;
+/* ----------------
+ * CustomPlan node
+ * ----------------
+ */
+struct CustomPlanMethods;
+
+struct CustomPath; /* to avoid to include nodes/relation.h here */
+struct PlannerInfo; /* to avoid to include nodes/relation.h here */
+
+typedef struct CustomPlan
+{
+ Scan scan;
+ int flags; /* CPPFLAG_* defined in pg_custom_plan_provider.h */
+ const struct CustomPlanMethods *methods;
+} CustomPlan;
+
+typedef struct CustomPlanMethods
+{
+ const char *CustomName;
+ Plan *(*InitCustomPlan)(CustomPlan *custom_plan,
+ struct PlannerInfo *root,
+ struct CustomPath *best_path,
+ List *tlist,
+ List *clauses);
+ void (*SetCustomPlanRef)(struct PlannerInfo *root,
+ CustomPlan *custom_plan,
+ int rtoffset);
+ void (*FinalizeCustomPlan)(struct PlannerInfo *root,
+ CustomPlan *custom_plan,
+ bool (*finalize_primnode)(),
+ void *finalize_context);
+ Node *(*CreateCustomPlanState)(CustomPlan *custom_plan);
+ void (*TextOutCustomPlan)(StringInfo str,
+ const CustomPlan *node);
+ CustomPlan *(*CopyCustomPlan)(const CustomPlan *from);
+} CustomPlanMethods;
/*
* ==========
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index dacbe9c..8fb8477 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -15,6 +15,7 @@
#define RELATION_H
#include "access/sdir.h"
+#include "lib/stringinfo.h"
#include "nodes/params.h"
#include "nodes/parsenodes.h"
#include "storage/block.h"
@@ -881,6 +882,32 @@ typedef struct ForeignPath
} ForeignPath;
/*
+ * CustomPath represents a scan using custom logic
+ *
+ * Extension (that performs as custom-plan provider) can adds an alternative
+ * path using its custom type being delivered from CustomPath.
+ * They can store their private data on the extra fields of their custom
+ * object. A set of common methods are represented as function pointers in
+ * CustomPathMethods structure; extension has to set up then correctly.
+ */
+struct CustomPathMethods;
+
+typedef struct CustomPath
+{
+ Path path;
+ int flags; /* CPPFLAG_* defined in pg_custom_plan_provider.h */
+ const struct CustomPathMethods *methods;
+} CustomPath;
+
+typedef struct CustomPathMethods
+{
+ const char *CustomName;
+ Node *(*CreateCustomPlan)(PlannerInfo *root,
+ CustomPath *best_path);
+ void (*TextOutCustomPath)(StringInfo str, const CustomPath *node);
+} CustomPathMethods;
+
+/*
* AppendPath represents an Append plan, ie, successive execution of
* several member plans.
*
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index a0bcc82..7cf331c 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -129,6 +129,20 @@ extern Path *reparameterize_path(PlannerInfo *root, Path *path,
double loop_count);
/*
+ * interface towards custom-plan provider functions
+ */
+typedef struct {
+ uint32 cpp_class;
+ PlannerInfo *root;
+ RelOptInfo *baserel;
+ RangeTblEntry *rte;
+} customScanArg;
+
+extern void call_custom_scan_providers(PlannerInfo *root,
+ RelOptInfo *baserel,
+ RangeTblEntry *rte);
+
+/*
* prototypes for relnode.c
*/
extern void setup_simple_rel_arrays(PlannerInfo *root);
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 8bdb7db..76e3c86 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -86,6 +86,7 @@ extern ModifyTable *make_modifytable(PlannerInfo *root,
List *withCheckOptionLists, List *returningLists,
List *rowMarks, int epqParam);
extern bool is_projection_capable_plan(Plan *plan);
+extern Node *replace_nestloop_params(PlannerInfo *root, Node *expr);
/*
* prototypes for plan/initsplan.c
@@ -128,6 +129,7 @@ extern List *remove_useless_joins(PlannerInfo *root, List *joinlist);
*/
extern Plan *set_plan_references(PlannerInfo *root, Plan *plan);
extern void fix_opfuncids(Node *node);
+extern Node *fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset);
extern void set_opfuncid(OpExpr *opexpr);
extern void set_sa_opfuncid(ScalarArrayOpExpr *opexpr);
extern void record_plan_function_dependency(PlannerInfo *root, Oid funcid);
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index b52e507..05603e5 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -107,6 +107,7 @@ PG_KEYWORD("current_time", CURRENT_TIME, RESERVED_KEYWORD)
PG_KEYWORD("current_timestamp", CURRENT_TIMESTAMP, RESERVED_KEYWORD)
PG_KEYWORD("current_user", CURRENT_USER, RESERVED_KEYWORD)
PG_KEYWORD("cursor", CURSOR, UNRESERVED_KEYWORD)
+PG_KEYWORD("custom", CUSTOM, UNRESERVED_KEYWORD)
PG_KEYWORD("cycle", CYCLE, UNRESERVED_KEYWORD)
PG_KEYWORD("data", DATA_P, UNRESERVED_KEYWORD)
PG_KEYWORD("database", DATABASE, UNRESERVED_KEYWORD)
@@ -282,6 +283,7 @@ PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD)
PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD)
PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD)
PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD)
+PG_KEYWORD("plan", PLAN, UNRESERVED_KEYWORD)
PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD)
PG_KEYWORD("position", POSITION, COL_NAME_KEYWORD)
PG_KEYWORD("preceding", PRECEDING, UNRESERVED_KEYWORD)
@@ -295,6 +297,7 @@ PG_KEYWORD("privileges", PRIVILEGES, UNRESERVED_KEYWORD)
PG_KEYWORD("procedural", PROCEDURAL, UNRESERVED_KEYWORD)
PG_KEYWORD("procedure", PROCEDURE, UNRESERVED_KEYWORD)
PG_KEYWORD("program", PROGRAM, UNRESERVED_KEYWORD)
+PG_KEYWORD("provider", PROVIDER, UNRESERVED_KEYWORD)
PG_KEYWORD("quote", QUOTE, UNRESERVED_KEYWORD)
PG_KEYWORD("range", RANGE, UNRESERVED_KEYWORD)
PG_KEYWORD("read", READ, UNRESERVED_KEYWORD)
@@ -325,6 +328,7 @@ PG_KEYWORD("row", ROW, COL_NAME_KEYWORD)
PG_KEYWORD("rows", ROWS, UNRESERVED_KEYWORD)
PG_KEYWORD("rule", RULE, UNRESERVED_KEYWORD)
PG_KEYWORD("savepoint", SAVEPOINT, UNRESERVED_KEYWORD)
+PG_KEYWORD("scan", SCAN, UNRESERVED_KEYWORD)
PG_KEYWORD("schema", SCHEMA, UNRESERVED_KEYWORD)
PG_KEYWORD("scroll", SCROLL, UNRESERVED_KEYWORD)
PG_KEYWORD("search", SEARCH, UNRESERVED_KEYWORD)
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index f97229f..e1e56f7 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -52,6 +52,8 @@ enum SysCacheIdentifier
CONNAMENSP,
CONSTROID,
CONVOID,
+ CUSTOMPLANPROVIDEROID,
+ CUSTOMPLANPROVIDERNAME,
DATABASEOID,
DEFACLROLENSPOBJ,
ENUMOID,
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 111d24c..5ebceea 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -97,6 +97,7 @@ pg_class|t
pg_collation|t
pg_constraint|t
pg_conversion|t
+pg_custom_plan_provider|t
pg_database|t
pg_db_role_setting|t
pg_default_acl|t