pgsql-v9.4-custom-scan-apis.v1.patch
application/octet-stream
Filename: pgsql-v9.4-custom-scan-apis.v1.patch
Type: application/octet-stream
Part: 1
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
Series: patch v9
| File | + | − |
|---|---|---|
| contrib/ctidscan/ctidscan.c | 761 | 0 |
| contrib/ctidscan/Makefile | 14 | 0 |
| contrib/Makefile | 1 | 0 |
| doc/src/sgml/ctidscan.sgml | 107 | 0 |
| src/backend/commands/explain.c | 78 | 0 |
| src/backend/executor/execAmi.c | 32 | 2 |
| src/backend/executor/execProcnode.c | 14 | 0 |
| src/backend/executor/execQual.c | 5 | 5 |
| src/backend/executor/execUtils.c | 2 | 2 |
| src/backend/executor/Makefile | 1 | 1 |
| src/backend/executor/nodeCustom.c | 252 | 0 |
| src/backend/nodes/bitmapset.c | 62 | 0 |
| src/backend/nodes/copyfuncs.c | 30 | 0 |
| src/backend/nodes/outfuncs.c | 19 | 0 |
| src/backend/nodes/print.c | 4 | 0 |
| src/backend/optimizer/path/allpaths.c | 23 | 0 |
| src/backend/optimizer/path/costsize.c | 2 | 5 |
| src/backend/optimizer/path/joinpath.c | 18 | 0 |
| src/backend/optimizer/plan/createplan.c | 103 | 0 |
| src/backend/optimizer/plan/setrefs.c | 26 | 1 |
| src/backend/optimizer/plan/subselect.c | 10 | 0 |
| src/backend/optimizer/util/pathnode.c | 40 | 0 |
| src/backend/utils/adt/ruleutils.c | 41 | 3 |
| src/include/catalog/pg_operator.h | 4 | 0 |
| src/include/executor/executor.h | 2 | 1 |
| src/include/executor/nodeCustom.h | 94 | 0 |
| src/include/nodes/bitmapset.h | 4 | 0 |
| src/include/nodes/execnodes.h | 17 | 0 |
| src/include/nodes/nodes.h | 3 | 0 |
| src/include/nodes/plannodes.h | 16 | 0 |
| src/include/nodes/primnodes.h | 1 | 0 |
| src/include/nodes/relation.h | 16 | 0 |
| src/include/optimizer/cost.h | 3 | 0 |
| src/include/optimizer/pathnode.h | 10 | 0 |
| src/include/optimizer/paths.h | 25 | 0 |
| src/include/optimizer/planmain.h | 1 | 0 |
| src/test/regress/GNUmakefile | 12 | 3 |
| src/test/regress/input/custom_scan.source | 49 | 0 |
| src/test/regress/output/custom_scan.source | 290 | 0 |
| src/test/regress/parallel_schedule | 1 | 1 |
| src/test/regress/serial_schedule | 1 | 0 |
contrib/Makefile | 1 +
contrib/ctidscan/Makefile | 14 +
contrib/ctidscan/ctidscan.c | 761 +++++++++++++++++++++++++++++
doc/src/sgml/ctidscan.sgml | 107 ++++
src/backend/commands/explain.c | 78 +++
src/backend/executor/Makefile | 2 +-
src/backend/executor/execAmi.c | 34 +-
src/backend/executor/execProcnode.c | 14 +
src/backend/executor/execQual.c | 10 +-
src/backend/executor/execUtils.c | 4 +-
src/backend/executor/nodeCustom.c | 252 ++++++++++
src/backend/nodes/bitmapset.c | 62 +++
src/backend/nodes/copyfuncs.c | 30 ++
src/backend/nodes/outfuncs.c | 19 +
src/backend/nodes/print.c | 4 +
src/backend/optimizer/path/allpaths.c | 23 +
src/backend/optimizer/path/costsize.c | 7 +-
src/backend/optimizer/path/joinpath.c | 18 +
src/backend/optimizer/plan/createplan.c | 103 ++++
src/backend/optimizer/plan/setrefs.c | 27 +-
src/backend/optimizer/plan/subselect.c | 10 +
src/backend/optimizer/util/pathnode.c | 40 ++
src/backend/utils/adt/ruleutils.c | 44 +-
src/include/catalog/pg_operator.h | 4 +
src/include/executor/executor.h | 3 +-
src/include/executor/nodeCustom.h | 94 ++++
src/include/nodes/bitmapset.h | 4 +
src/include/nodes/execnodes.h | 17 +
src/include/nodes/nodes.h | 3 +
src/include/nodes/plannodes.h | 16 +
src/include/nodes/primnodes.h | 1 +
src/include/nodes/relation.h | 16 +
src/include/optimizer/cost.h | 3 +
src/include/optimizer/pathnode.h | 10 +
src/include/optimizer/paths.h | 25 +
src/include/optimizer/planmain.h | 1 +
src/test/regress/GNUmakefile | 15 +-
src/test/regress/input/custom_scan.source | 49 ++
src/test/regress/output/custom_scan.source | 290 +++++++++++
src/test/regress/parallel_schedule | 2 +-
src/test/regress/serial_schedule | 1 +
41 files changed, 2194 insertions(+), 24 deletions(-)
diff --git a/contrib/Makefile b/contrib/Makefile
index 8a2a937..703e5a5 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..708c5b7
--- /dev/null
+++ b/contrib/ctidscan/Makefile
@@ -0,0 +1,14 @@
+# contrib/ctidscan/Makefile
+
+MODULES = ctidscan
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/ctidscan
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/ctidscan/ctidscan.c b/contrib/ctidscan/ctidscan.c
new file mode 100644
index 0000000..c392051
--- /dev/null
+++ b/contrib/ctidscan/ctidscan.c
@@ -0,0 +1,761 @@
+/*
+ * 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 chepest 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) 2013, PostgreSQL Global Development Group
+ */
+#include "postgres.h"
+#include "access/relscan.h"
+#include "access/sysattr.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_type.h"
+#include "executor/nodeCustom.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "optimizer/cost.h"
+#include "optimizer/paths.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/planmain.h"
+#include "optimizer/restrictinfo.h"
+#include "storage/bufmgr.h"
+#include "storage/itemptr.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/spccache.h"
+
+extern void _PG_init(void);
+
+PG_MODULE_MAGIC;
+
+static add_scan_path_hook_type add_scan_path_next;
+
+#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 chainned 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,
+ CustomPath *cpath)
+{
+ List *ctidquals = cpath->custom_private;
+ ListCell *lc;
+ double ntuples;
+ ItemPointerData ip_min;
+ ItemPointerData ip_max;
+ bool has_min_val = false;
+ bool has_max_val = false;
+ BlockNumber num_pages;
+ Cost startup_cost = 0;
+ Cost run_cost = 0;
+ Cost cpu_per_tuple;
+ QualCost qpqual_cost;
+ QualCost ctid_qual_cost;
+ double spc_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 (cpath->path.param_info)
+ cpath->path.rows = cpath->path.param_info->ppi_rows;
+ else
+ cpath->path.rows = baserel->rows;
+
+ /* Estimate how many tuples we may retrieve */
+ ItemPointerSet(&ip_min, 0, 0);
+ ItemPointerSet(&ip_max, MaxBlockNumber, MaxOffsetNumber);
+ foreach (lc, ctidquals)
+ {
+ OpExpr *op = lfirst(lc);
+ Oid opno;
+ Node *other;
+
+ Assert(is_opclause(op));
+ if (IsCTIDVar(linitial(op->args), baserel->relid))
+ {
+ opno = op->opno;
+ other = lsecond(op->args);
+ }
+ else if (IsCTIDVar(lsecond(op->args), baserel->relid))
+ {
+ /* To simplifies, we assume as if Var node is 1st argument */
+ opno = get_commutator(op->opno);
+ other = linitial(op->args);
+ }
+ else
+ elog(ERROR, "could not identify CTID variable");
+
+ if (IsA(other, Const))
+ {
+ ItemPointer ip = (ItemPointer)(((Const *) other)->constvalue);
+
+ /*
+ * Just an rough estimation, we don't distinct inequality and
+ * inequality-or-equal operator.
+ */
+ switch (opno)
+ {
+ case TIDLessOperator:
+ case TIDLessEqualOperator:
+ if (ItemPointerCompare(ip, &ip_max) < 0)
+ ItemPointerCopy(ip, &ip_max);
+ has_max_val = true;
+ break;
+ case TIDGreaterOperator:
+ case TIDGreaterEqualOperator:
+ if (ItemPointerCompare(ip, &ip_min) > 0)
+ ItemPointerCopy(ip, &ip_min);
+ has_min_val = true;
+ break;
+ default:
+ elog(ERROR, "unexpected operator code: %u", op->opno);
+ break;
+ }
+ }
+ }
+
+ /* estimated number of tuples in this relation */
+ ntuples = baserel->pages * baserel->tuples;
+
+ if (has_min_val && has_max_val)
+ {
+ /* case of both side being bounded */
+ BlockNumber bnum_max = BlockIdGetBlockNumber(&ip_max.ip_blkid);
+ BlockNumber bnum_min = BlockIdGetBlockNumber(&ip_min.ip_blkid);
+
+ bnum_max = Min(bnum_max, baserel->pages);
+ bnum_min = Max(bnum_min, 0);
+ num_pages = Min(bnum_max - bnum_min + 1, 1);
+ }
+ else if (has_min_val)
+ {
+ /* case of only lower side being bounded */
+ BlockNumber bnum_max = baserel->pages;
+ BlockNumber bnum_min = BlockIdGetBlockNumber(&ip_min.ip_blkid);
+
+ bnum_min = Max(bnum_min, 0);
+ num_pages = Min(bnum_max - bnum_min + 1, 1);
+ }
+ else if (has_max_val)
+ {
+ /* case of only upper side being bounded */
+ BlockNumber bnum_max = BlockIdGetBlockNumber(&ip_max.ip_blkid);
+ BlockNumber bnum_min = 0;
+
+ bnum_max = Min(bnum_max, baserel->pages);
+ num_pages = Min(bnum_max - bnum_min + 1, 1);
+ }
+ else
+ {
+ /*
+ * Just a rough estimation. We assume half of records shall be
+ * read using this restriction clause, but undeterministic untill
+ * executor run it actually.
+ */
+ num_pages = Max((baserel->pages + 1) / 2, 1);
+ }
+ ntuples *= ((double) num_pages) / ((double) baserel->pages);
+
+ /*
+ * The TID qual expressions will be computed once, any other baserestrict
+ * quals once per retrived tuple.
+ */
+ cost_qual_eval(&ctid_qual_cost, ctidquals, 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 */
+ get_restriction_qual_cost(root, baserel,
+ cpath->path.param_info,
+ &qpqual_cost);
+
+ /*
+ * 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;
+
+ cpath->path.startup_cost = startup_cost;
+ cpath->path.total_cost = startup_cost + run_cost;
+}
+
+/*
+ * CTidAddScanPath
+ *
+ * It adds a custom scan path if inequality operators are given on the
+ * relation to be scanned and makes sense to reduce number of tuples.
+ */
+static void
+CTidAddScanPath(PlannerInfo *root,
+ RelOptInfo *baserel,
+ RangeTblEntry *rte)
+{
+ char relkind;
+ List *rlst = NIL;
+ ListCell *lc;
+
+ /* Gives another extensions chance to add a path */
+ if (add_scan_path_next)
+ (*add_scan_path_next)(root, baserel, rte);
+
+ /* All we support is regular relations */
+ if (rte->rtekind != RTE_RELATION)
+ return;
+ relkind = get_rel_relkind(rte->relid);
+ if (relkind != RELKIND_RELATION &&
+ relkind != RELKIND_MATVIEW &&
+ relkind != RELKIND_TOASTVALUE)
+ return;
+
+ /* walk on the restrict info */
+ foreach (lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+ List *temp;
+
+ if (!IsA(rinfo, RestrictInfo))
+ continue; /* probably should never happen */
+ temp = CTidQualFromExpr((Node *) rinfo->clause, baserel->relid);
+ rlst = list_concat(rlst, 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 (rlst != NIL)
+ {
+ CustomPath *cpath = makeNode(CustomPath);
+ Relids required_outer;
+
+ /*
+ * We don't support pushing join clauses into the quals of a ctidscan,
+ * but it could still have required parameterization due to LATERAL
+ * refs in its tlist.
+ */
+ required_outer = baserel->lateral_relids;
+
+ cpath->path.pathtype = T_CustomScan;
+ cpath->path.parent = baserel;
+ cpath->path.param_info = get_baserel_parampathinfo(root, baserel,
+ required_outer);
+ cpath->custom_name = pstrdup("ctidscan");
+ cpath->custom_flags = CUSTOM__SUPPORT_BACKWARD_SCAN;
+ cpath->custom_private = rlst;
+
+ CTidEstimateCosts(root, baserel, cpath);
+
+ add_path(baserel, &cpath->path);
+ }
+}
+
+/*
+ * CTidInitCustomScanPlan
+ *
+ * It initializes the given CustomScan plan object according to the CustomPath
+ * being choosen by the optimizer.
+ */
+static void
+CTidInitCustomScanPlan(PlannerInfo *root,
+ CustomScan *cscan_plan,
+ CustomPath *cscan_path,
+ List *tlist,
+ List *scan_clauses)
+{
+ Index scan_relid = cscan_path->path.parent->relid;
+ List *ctidquals = cscan_path->custom_private;
+
+ /* should be a base relation */
+ Assert(scan_relid > 0);
+ Assert(cscan_path->path.parent->rtekind == RTE_RELATION);
+
+ /* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
+ scan_clauses = extract_actual_clauses(scan_clauses, false);
+
+ /*
+ * Most of initialization stuff was done at nodeCustomScan.c. So, all
+ * we need to do is to put clauses that were little bit adjusted and
+ * private stuff; list of restriction clauses in this case.
+ */
+ cscan_plan->scan.plan.targetlist = tlist;
+ cscan_plan->scan.plan.qual = scan_clauses;
+ cscan_plan->custom_private = ctidquals;
+}
+
+/*
+ * CTidScanState
+ *
+ * State of custom-tid scan during its execution.
+ */
+typedef struct {
+ Index scanrelid; /* range table index of the relation */
+ ItemPointerData ip_min; /* minimum ItemPointer */
+ ItemPointerData ip_max; /* maximum ItemPointer */
+ int32 ip_min_comp; /* comparison policy to ip_min */
+ int32 ip_max_comp; /* comparison policy to ip_max */
+ bool ip_needs_eval; /* true, if needs to seek again */
+ List *ctid_quals; /* list of ExprState for inequality ops */
+} CTidScanState;
+
+static bool
+CTidEvalScanZone(CustomScanState *node)
+{
+ CTidScanState *ctss = node->custom_state;
+ ExprContext *econtext = node->ss.ps.ps_ExprContext;
+ ListCell *lc;
+
+ /*
+ * See ItemPointerCompare(), ip_max_comp shall be usually either 1 or
+ * 0 if tid of fetched records are larger than or equal with ip_min.
+ * To detect end of scan, we shall check whether the result of
+ * ItemPointerCompare() is less than ip_max_comp, so it never touch
+ * the point if ip_max_comp is -1, because all the result is either
+ * 1, 0 or -1. So, it is same as "open ended" as if no termination
+ * condition was set.
+ */
+ ctss->ip_min_comp = -1;
+ ctss->ip_max_comp = 1;
+
+ /* 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);
+ Oid opno;
+ ExprState *exstate;
+ ItemPointer itemptr;
+ bool isnull;
+
+ if (IsCTIDVar(arg1, ctss->scanrelid))
+ {
+ exstate = (ExprState *) lsecond(fexstate->args);
+ opno = op->opno;
+ }
+ else if (IsCTIDVar(arg2, ctss->scanrelid))
+ {
+ exstate = (ExprState *) linitial(fexstate->args);
+ opno = get_commutator(op->opno);
+ }
+ else
+ elog(ERROR, "could not identify CTID variable");
+
+ itemptr = (ItemPointer)
+ DatumGetPointer(ExecEvalExprSwitchContext(exstate,
+ econtext,
+ &isnull,
+ NULL));
+ if (!isnull)
+ {
+ /*
+ * OK, we could calculate a particular TID that should be
+ * larger than, less than or equal with fetched record, thus,
+ * it allows to determine upper or lower bounds of this scan.
+ */
+ switch (opno)
+ {
+ case TIDLessOperator:
+ if (ctss->ip_max_comp > 0 ||
+ ItemPointerCompare(itemptr, &ctss->ip_max) <= 0)
+ {
+ ItemPointerCopy(itemptr, &ctss->ip_max);
+ ctss->ip_max_comp = -1;
+ }
+ break;
+ case TIDLessEqualOperator:
+ if (ctss->ip_max_comp > 0 ||
+ ItemPointerCompare(itemptr, &ctss->ip_max) < 0)
+ {
+ ItemPointerCopy(itemptr, &ctss->ip_max);
+ ctss->ip_max_comp = 0;
+ }
+ break;
+ case TIDGreaterOperator:
+ if (ctss->ip_min_comp < 0 ||
+ ItemPointerCompare(itemptr, &ctss->ip_min) >= 0)
+ {
+ ItemPointerCopy(itemptr, &ctss->ip_min);
+ ctss->ip_min_comp = 0;
+ }
+ break;
+ case TIDGreaterEqualOperator:
+ if (ctss->ip_min_comp < 0 ||
+ ItemPointerCompare(itemptr, &ctss->ip_min) > 0)
+ {
+ ItemPointerCopy(itemptr, &ctss->ip_min);
+ ctss->ip_min_comp = 1;
+ }
+ break;
+ default:
+ elog(ERROR, "unsupported operator");
+ break;
+ }
+ }
+ else
+ {
+ /*
+ * Whole of the restriction clauses chainned with AND- boolean
+ * operators because false, if one of the clauses has NULL result.
+ * So, we can immediately break the evaluation to inform caller
+ * it does not make sense to scan any more.
+ */
+ return false;
+ }
+ }
+ return true;
+}
+
+/*
+ * CTidBeginCustomScan
+ *
+ * It initializes the given CustomScanState according to the CustomScan plan.
+ */
+static void
+CTidBeginCustomScan(CustomScanState *node, int eflags)
+{
+ CustomScan *cscan = (CustomScan *)node->ss.ps.plan;
+ Index scanrelid = ((Scan *)node->ss.ps.plan)->scanrelid;
+ EState *estate = node->ss.ps.state;
+ CTidScanState *ctss;
+
+ /* Do nothing anymore in EXPLAIN (no ANALYZE) case. */
+ if (eflags & EXEC_FLAG_EXPLAIN_ONLY)
+ return;
+
+ /* Begin sequential scan, but pointer shall be seeked later */
+ node->ss.ss_currentScanDesc
+ = heap_beginscan(node->ss.ss_currentRelation,
+ estate->es_snapshot, 0, NULL);
+
+ /* init CTidScanState */
+ ctss = palloc0(sizeof(CTidScanState));
+ ctss->scanrelid = scanrelid;
+ ctss->ctid_quals = (List *)
+ ExecInitExpr((Expr *)cscan->custom_private, &node->ss.ps);
+ ctss->ip_needs_eval = true;
+
+ node->custom_state = ctss;
+}
+
+/*
+ * CTidSeekPosition
+ *
+ * It seeks current scan position into a particular point we specified.
+ * Next heap_getnext() will fetch a record from the point we seeked.
+ * It returns false, if specified position was out of range thus does not
+ * make sense to scan any mode. Elsewhere, true shall be return.
+ */
+static bool
+CTidSeekPosition(HeapScanDesc scan, ItemPointer pos, ScanDirection direction)
+{
+ BlockNumber bnum = BlockIdGetBlockNumber(&pos->ip_blkid);
+ ItemPointerData save_mctid;
+ int save_mindex;
+
+ Assert(direction == BackwardScanDirection ||
+ direction == ForwardScanDirection);
+
+ /*
+ * In case when block-number is out of the range, it is obvious that
+ * no tuples shall be fetched if forward scan direction. On the other
+ * hand, we have nothing special for backward scan direction.
+ * Note that heap_getnext() shall return NULL tuple just after
+ * heap_rescan() if NoMovementScanDirection is given. Caller of this
+ * function override scan direction if 'true' was returned, so it makes
+ * this scan terminated immediately.
+ */
+ if (bnum >= scan->rs_nblocks)
+ {
+ heap_rescan(scan, NULL);
+ /* Termination of this scan immediately */
+ if (direction == ForwardScanDirection)
+ return true;
+ /* Elsewhere, backward scan from the beginning */
+ return false;
+ }
+
+ /* save the marked position */
+ ItemPointerCopy(&scan->rs_mctid, &save_mctid);
+ save_mindex = scan->rs_mindex;
+
+ /*
+ * Ensure the block that includes the position shall be loaded on
+ * heap_restrpos(). Because heap_restrpos() internally calls
+ * heapgettup() or heapgettup_pagemode() that kicks heapgetpage()
+ * when rs_cblock is different from the block number being pointed
+ * by rs_mctid, it makes sense to put invalid block number not to
+ * match previous value.
+ */
+ scan->rs_cblock = InvalidBlockNumber;
+
+ /* Put a pseudo value as if heap_markpos() save a position. */
+ ItemPointerCopy(pos, &scan->rs_mctid);
+ if (scan->rs_pageatatime)
+ scan->rs_mindex = ItemPointerGetOffsetNumber(pos) - 1;
+
+ /* Seek to the point */
+ heap_restrpos(scan);
+
+ /* restore the marked position */
+ ItemPointerCopy(&save_mctid, &scan->rs_mctid);
+ scan->rs_mindex = save_mindex;
+
+ return true;
+}
+
+/*
+ * CTidAccessCustomScan
+ *
+ * Access method of ExecScan(). It fetches a tuple from the underlying heap
+ * scan that was started from the point according to the tid clauses.
+ */
+static TupleTableSlot *
+CTidAccessCustomScan(CustomScanState *node)
+{
+ CTidScanState *ctss = node->custom_state;
+ HeapScanDesc scan = node->ss.ss_currentScanDesc;
+ TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
+ EState *estate = node->ss.ps.state;
+ ScanDirection direction = estate->es_direction;
+ HeapTuple tuple;
+
+ if (ctss->ip_needs_eval)
+ {
+ /* It terminates this scan, if result set shall be obvious empty. */
+ if (!CTidEvalScanZone(node))
+ return NULL;
+
+ if (direction == ForwardScanDirection)
+ {
+ /* seek to the point if min-tid was obvious */
+ if (ctss->ip_min_comp != -1)
+ {
+ if (CTidSeekPosition(scan, &ctss->ip_min, direction))
+ direction = NoMovementScanDirection;
+ }
+ else if (scan->rs_inited)
+ heap_rescan(scan, NULL);
+ }
+ else if (direction == BackwardScanDirection)
+ {
+ /* seel to the point if max-tid was obvious */
+ if (ctss->ip_max_comp != 1)
+ {
+ if (CTidSeekPosition(scan, &ctss->ip_max, direction))
+ direction = NoMovementScanDirection;
+ }
+ else if (scan->rs_inited)
+ heap_rescan(scan, NULL);
+ }
+ else
+ elog(ERROR, "unexpected scan direction");
+
+ ctss->ip_needs_eval = false;
+ }
+
+ /*
+ * get the next tuple from the table
+ */
+ tuple = heap_getnext(scan, direction);
+ if (!HeapTupleIsValid(tuple))
+ return NULL;
+
+ /*
+ * check whether the fetched tuple reached to the upper bound
+ * if forward scan, or the lower bound if backward scan.
+ */
+ if (direction == ForwardScanDirection)
+ {
+ if (ItemPointerCompare(&tuple->t_self,
+ &ctss->ip_max) > ctss->ip_max_comp)
+ return NULL;
+ }
+ else if (direction == BackwardScanDirection)
+ {
+ if (ItemPointerCompare(&scan->rs_ctup.t_self,
+ &ctss->ip_min) < ctss->ip_min_comp)
+ return NULL;
+ }
+ ExecStoreTuple(tuple, slot, scan->rs_cbuf, false);
+
+ return slot;
+}
+
+/*
+ * CTidRecheckCustomScan
+ *
+ * Recheck method of ExecScan(). We don't need recheck logic.
+ */
+static bool
+CTidRecheckCustomScan(CustomScanState *node, TupleTableSlot *slot)
+{
+ return true;
+}
+
+/*
+ * CTidExecCustomScan
+ *
+ * It fetches a tuple from the underlying heap scan, according to
+ * the Execscan() manner.
+ */
+static TupleTableSlot *
+CTidExecCustomScan(CustomScanState *node)
+{
+ return ExecScan(&node->ss,
+ (ExecScanAccessMtd) CTidAccessCustomScan,
+ (ExecScanRecheckMtd) CTidRecheckCustomScan);
+}
+
+/*
+ * CTidEndCustomScan
+ *
+ * It terminates custom tid scan.
+ */
+static void
+CTidEndCustomScan(CustomScanState *node)
+{
+ CTidScanState *ctss = node->custom_state;
+
+ /* if ctss != NULL, we started underlying heap-scan */
+ if (ctss)
+ heap_endscan(node->ss.ss_currentScanDesc);
+}
+
+/*
+ * CTidReScanCustomScan
+ *
+ * It rewinds current position of the scan. Setting ip_needs_eval indicates
+ * to calculate the starting point again and rewinds underlying heap scan
+ * on the next ExecScan timing.
+ */
+static void
+CTidReScanCustomScan(CustomScanState *node)
+{
+ CTidScanState *ctss = node->custom_state;
+
+ ctss->ip_needs_eval = true;
+
+ ExecScanReScan(&node->ss);
+}
+
+/*
+ * Entrypoint of this extension
+ */
+void
+_PG_init(void)
+{
+ CustomProvider provider;
+
+ /* registration of callback on add scan path */
+ add_scan_path_next = add_scan_path_hook;
+ add_scan_path_hook = CTidAddScanPath;
+
+ /* registration of custom scan provider */
+ memset(&provider, 0, sizeof(provider));
+ snprintf(provider.name, sizeof(provider.name), "ctidscan");
+ provider.InitCustomScanPlan = CTidInitCustomScanPlan;
+ provider.BeginCustomScan = CTidBeginCustomScan;
+ provider.ExecCustomScan = CTidExecCustomScan;
+ provider.EndCustomScan = CTidEndCustomScan;
+ provider.ReScanCustomScan = CTidReScanCustomScan;
+
+ register_custom_provider(&provider);
+}
diff --git a/doc/src/sgml/ctidscan.sgml b/doc/src/sgml/ctidscan.sgml
new file mode 100644
index 0000000..60081f7
--- /dev/null
+++ b/doc/src/sgml/ctidscan.sgml
@@ -0,0 +1,107 @@
+<!-- doc/src/sgml/ctidscan.sgml -->
+
+<sect1 id="ctidscan" xreflabel="ctidscan">
+ <title>lo</title>
+
+ <indexterm zone="ctidscan">
+ <primary>ctidscan</primary>
+ </indexterm>
+
+ <para>
+ The <filename>ctidscan</> module provides an additional logic to scan
+ regular relations if <literal>WHERE</> clause contains inequality
+ operators that compares something with <literal>ctid</> system column.
+ It also performs as a proof-of-concept implementation that works on
+ the custom-scan APIs that enables to extend the core executor system.
+ </para>
+
+ <sect2>
+ <title>Overview</title>
+ <para>
+ Once this module is loaded, it registers itself as a custom-scan provider.
+ It allows to provide an additional scan path on regular relations using
+ qualifiers that reference <literal>ctid</> system column.
+ </para>
+ <para>
+ For example, the query below usually falls to sequential scan if this
+ module was not loaded.
+<programlisting>
+SELECT ctid,* FROM my_table WHERE ctid > '(100,0)'::tid;
+</programlisting>
+ On the other hand, <filename>ctidscan</> module can construct an alternative
+ scan plan utilizing inequality operators that involve <literal>ctid</>
+ system column, to reduce number of rows to be processed.
+ It does not make sense obviously to read tuples within pages being located
+ on 99th page or prior. So, it seeks the internal pointer to scan into
+ <literal>(100,0)</> at beginning of the scan, even though it internally
+ uses same logic with sequential scan.
+ </para>
+ <para>
+ Usually, <productname>PostgreSQL</> runs queries with inequality operators
+ that involves <literal>ctid</> system column using sequential scan, as
+ follows.
+<programlisting>
+postgres=# EXPLAIN SELECT * FROM t1 WHERE ctid > '(100,0)'::tid;
+ QUERY PLAN
+--------------------------------------------------------
+ Seq Scan on t1 (cost=0.00..209.00 rows=3333 width=37)
+ Filter: (ctid > '(100,0)'::tid)
+(2 rows)
+</programlisting>
+ It works well except for the waste of i/o loads on the pages that contains
+ the records to be skipped.
+ </para>
+ <para>
+ On the other hands, an alternative scan path implemented with
+ <filename>ctidscan</> provides more efficient way; that skips the first
+ 100 pages prior to sequencial scan, as follows.
+<programlisting>
+postgres=# load 'ctidscan';
+LOAD
+postgres=# EXPLAIN SELECT * FROM t1 WHERE ctid > '(100,0)'::tid;
+ QUERY PLAN
+----------------------------------------------------------------------
+ Custom Scan (ctidscan) on t1 (cost=0.00..100.00 rows=3333 width=37)
+ Filter: (ctid > '(100,0)'::tid)
+(2 rows)
+</programlisting>
+ The optimizer internally compares all the candidates of scan paths, then
+ chooses a path with cheapest cost. The custom-scan path provided by
+ <filename>ctidscan</> is usually cheaper than sequential scan because of
+ smaller number of tuples to be processed.
+ </para>
+ <para>
+ Of course, it shall not be choosen if we have more cheaper path than the
+ above custom-scan path. Index-scan based on equality operation is usually
+ cheaper than this custom-scan, so optimizer adopts it instead of sequential
+ scan or custom scan provided by <filename>ctidscan</> for instance.
+<programlisting>
+postgres=# EXPLAIN SELECT * FROM t1 WHERE ctid > '(100,0)'::tid AND a = 100;
+ QUERY PLAN
+-------------------------------------------------------------------
+ Index Scan using t1_pkey on t1 (cost=0.29..8.30 rows=1 width=37)
+ Index Cond: (a = 100)
+ Filter: (ctid > '(100,0)'::tid)
+(3 rows)
+</programlisting>
+ </para>
+ <para>
+ Its usage is quite simple. All you need to do is, loading
+ the <filename>ctidscan</> into <productname>PostgreSQL</> using
+ <xref linkend="sql-load"> command,
+ <xref linkend="guc-shared-preload-libraries"> or
+ <xref linkend="guc-local-preload-libraries"> parameter, according to
+ your convenience.
+ </para>
+ <para>
+ We have no configurable parameter in this module, right now.
+ </para>
+ </sect2>
+ <sect2>
+ <title>Author</title>
+ <para>
+ KaiGai Kohei <email>kaigai@kaigai.gr.jp</email>
+ </para>
+ </sect2>
+
+</sect1>
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 4e93df2..39d2c12 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -19,6 +19,7 @@
#include "commands/defrem.h"
#include "commands/prepare.h"
#include "executor/hashjoin.h"
+#include "executor/nodeCustom.h"
#include "foreign/fdwapi.h"
#include "optimizer/clauses.h"
#include "parser/parsetree.h"
@@ -84,6 +85,7 @@ static void show_hash_info(HashState *hashstate, ExplainState *es);
static void show_instrumentation_count(const char *qlabel, int which,
PlanState *planstate, ExplainState *es);
static void show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es);
+static void show_customscan_info(CustomScanState *cstate, ExplainState *es);
static const char *explain_get_index_name(Oid indexId);
static void ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir,
ExplainState *es);
@@ -683,6 +685,11 @@ ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
*rels_used = bms_add_member(*rels_used,
((Scan *) plan)->scanrelid);
break;
+ case T_CustomScan:
+ if (((Scan *) plan)->scanrelid > 0)
+ *rels_used = bms_add_member(*rels_used,
+ ((Scan *) plan)->scanrelid);
+ break;
case T_ModifyTable:
/* cf ExplainModifyTarget */
*rels_used = bms_add_member(*rels_used,
@@ -809,6 +816,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
const char *sname; /* node type name for non-text output */
const char *strategy = NULL;
const char *operation = NULL;
+ char namebuf[NAMEDATALEN + 32];
int save_indent = es->indent;
bool haschildren;
@@ -897,6 +905,13 @@ ExplainNode(PlanState *planstate, List *ancestors,
case T_ForeignScan:
pname = sname = "Foreign Scan";
break;
+ case T_CustomScan:
+ snprintf(namebuf, sizeof(namebuf), "Custom Scan (%s)",
+ ((CustomScan *) plan)->custom_name);
+ pname = pstrdup(namebuf);
+ sname = "Custom Scan";
+ operation = ((CustomScan *) plan)->custom_name;
+ break;
case T_Material:
pname = sname = "Materialize";
break;
@@ -1013,6 +1028,10 @@ ExplainNode(PlanState *planstate, List *ancestors,
case T_ForeignScan:
ExplainScanTarget((Scan *) plan, es);
break;
+ case T_CustomScan:
+ if (((Scan *) plan)->scanrelid > 0)
+ ExplainScanTarget((Scan *) plan, es);
+ break;
case T_IndexScan:
{
IndexScan *indexscan = (IndexScan *) plan;
@@ -1291,6 +1310,17 @@ ExplainNode(PlanState *planstate, List *ancestors,
planstate, es);
show_foreignscan_info((ForeignScanState *) planstate, es);
break;
+ case T_CustomScan:
+ if (((CustomScan *)plan)->funcexpr != NULL && es->verbose)
+ show_expression(((CustomScan *)plan)->funcexpr,
+ "Function Call", planstate, ancestors,
+ es->verbose, es);
+ show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
+ if (plan->qual)
+ show_instrumentation_count("Rows Removed by Filter", 1,
+ planstate, es);
+ show_customscan_info((CustomScanState *) planstate, es);
+ break;
case T_NestLoop:
show_upper_qual(((NestLoop *) plan)->join.joinqual,
"Join Filter", planstate, ancestors, es);
@@ -1858,6 +1888,19 @@ show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es)
}
/*
+ * Show extra information for a CustomScan node.
+ */
+static void
+show_customscan_info(CustomScanState *cstate, ExplainState *es)
+{
+ CustomProvider *provider = cstate->custom_provider;
+
+ /* Let custom scan provider emit whatever fields it wants */
+ if (provider->ExplainCustomScan != NULL)
+ provider->ExplainCustomScan(cstate, es);
+}
+
+/*
* Fetch the name of an index in an EXPLAIN
*
* We allow plugins to get control here so that plans involving hypothetical
@@ -2025,6 +2068,41 @@ ExplainTargetRel(Plan *plan, Index rti, ExplainState *es)
objectname = rte->ctename;
objecttag = "CTE Name";
break;
+ case T_CustomScan:
+ if (rte->rtekind == RTE_RELATION)
+ {
+ objectname = get_rel_name(rte->relid);
+ if (es->verbose)
+ namespace =
+ get_namespace_name(get_rel_namespace(rte->relid));
+ objecttag = "Relation Name";
+ }
+ else if (rte->rtekind == RTE_JOIN)
+ {
+ objectname = rte->eref->aliasname;
+ objecttag = "Join Alias";
+ }
+ else if (rte->rtekind == RTE_FUNCTION)
+ {
+ Node *funcexpr = ((CustomScan *) plan)->funcexpr;
+
+ if (funcexpr && IsA(funcexpr, FuncExpr))
+ {
+ Oid funcid = ((FuncExpr *) funcexpr)->funcid;
+
+ objectname = get_func_name(funcid);
+ if (es->verbose)
+ namespace =
+ get_namespace_name(get_func_namespace(funcid));
+ }
+ objecttag = "Function Name";
+ }
+ else if (rte->rtekind == RTE_CTE)
+ {
+ objectname = rte->ctename;
+ objecttag = "CTE Name";
+ }
+ break;
default:
break;
}
diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile
index 6081b56..4dece5a 100644
--- a/src/backend/executor/Makefile
+++ b/src/backend/executor/Makefile
@@ -15,7 +15,7 @@ include $(top_builddir)/src/Makefile.global
OBJS = execAmi.o execCurrent.o execGrouping.o execJunk.o execMain.o \
execProcnode.o execQual.o execScan.o execTuples.o \
execUtils.o functions.o instrument.o nodeAppend.o nodeAgg.o \
- nodeBitmapAnd.o nodeBitmapOr.o \
+ nodeBitmapAnd.o nodeBitmapOr.o nodeCustom.o \
nodeBitmapHeapscan.o nodeBitmapIndexscan.o nodeHash.o \
nodeHashjoin.o nodeIndexscan.o nodeIndexonlyscan.o \
nodeLimit.o nodeLockRows.o \
diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c
index a078104..f80e6c4 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -21,6 +21,7 @@
#include "executor/nodeBitmapIndexscan.h"
#include "executor/nodeBitmapOr.h"
#include "executor/nodeCtescan.h"
+#include "executor/nodeCustom.h"
#include "executor/nodeForeignscan.h"
#include "executor/nodeFunctionscan.h"
#include "executor/nodeGroup.h"
@@ -197,6 +198,10 @@ ExecReScan(PlanState *node)
ExecReScanForeignScan((ForeignScanState *) node);
break;
+ case T_CustomScanState:
+ ExecReScanCustomScan((CustomScanState *) node);
+ break;
+
case T_NestLoopState:
ExecReScanNestLoop((NestLoopState *) node);
break;
@@ -291,6 +296,10 @@ ExecMarkPos(PlanState *node)
ExecValuesMarkPos((ValuesScanState *) node);
break;
+ case T_CustomScanState:
+ ExecCustomMarkPos((CustomScanState *) node);
+ break;
+
case T_MaterialState:
ExecMaterialMarkPos((MaterialState *) node);
break;
@@ -348,6 +357,10 @@ ExecRestrPos(PlanState *node)
ExecValuesRestrPos((ValuesScanState *) node);
break;
+ case T_CustomScanState:
+ ExecCustomRestrPos((CustomScanState *) node);
+ break;
+
case T_MaterialState:
ExecMaterialRestrPos((MaterialState *) node);
break;
@@ -379,9 +392,9 @@ ExecRestrPos(PlanState *node)
* and valuesscan support is actually useless code at present.)
*/
bool
-ExecSupportsMarkRestore(NodeTag plantype)
+ExecSupportsMarkRestore(Path *path)
{
- switch (plantype)
+ switch (path->pathtype)
{
case T_SeqScan:
case T_IndexScan:
@@ -392,6 +405,14 @@ ExecSupportsMarkRestore(NodeTag plantype)
case T_Sort:
return true;
+ case T_CustomPath:
+ {
+ int flags = ((CustomPath *) path)->custom_flags;
+ if (flags & CUSTOM__SUPPORT_MARK_RESTORE)
+ return true;
+ return false;
+ }
+
case T_Result:
/*
@@ -465,6 +486,15 @@ ExecSupportsBackwardScan(Plan *node)
return ExecSupportsBackwardScan(((SubqueryScan *) node)->subplan) &&
TargetListSupportsBackwardScan(node->targetlist);
+ case T_CustomScan:
+ {
+ int flags = ((CustomScan *) node)->custom_flags;
+
+ if (flags & CUSTOM__SUPPORT_BACKWARD_SCAN)
+ return TargetListSupportsBackwardScan(node->targetlist);
+ }
+ 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 76dd62f..b1110b9 100644
--- a/src/backend/executor/execProcnode.c
+++ b/src/backend/executor/execProcnode.c
@@ -85,6 +85,7 @@
#include "executor/nodeBitmapIndexscan.h"
#include "executor/nodeBitmapOr.h"
#include "executor/nodeCtescan.h"
+#include "executor/nodeCustom.h"
#include "executor/nodeForeignscan.h"
#include "executor/nodeFunctionscan.h"
#include "executor/nodeGroup.h"
@@ -244,6 +245,11 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
estate, eflags);
break;
+ case T_CustomScan:
+ result = (PlanState *) ExecInitCustomScan((CustomScan *) node,
+ estate, eflags);
+ break;
+
/*
* join nodes
*/
@@ -442,6 +448,10 @@ ExecProcNode(PlanState *node)
result = ExecForeignScan((ForeignScanState *) node);
break;
+ case T_CustomScanState:
+ result = ExecCustomScan((CustomScanState *) node);
+ break;
+
/*
* join nodes
*/
@@ -678,6 +688,10 @@ ExecEndNode(PlanState *node)
ExecEndForeignScan((ForeignScanState *) node);
break;
+ case T_CustomScanState:
+ ExecEndCustomScan((CustomScanState *) node);
+ break;
+
/*
* join nodes
*/
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 90c2753..e60ac67 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -592,7 +592,7 @@ ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext,
slot = econtext->ecxt_outertuple;
break;
- /* INDEX_VAR is handled by default case */
+ /* INDEX_VAR and CUSTOM_VAR are handled by default case */
default: /* get the tuple from the relation being
* scanned */
@@ -680,7 +680,7 @@ ExecEvalScalarVarFast(ExprState *exprstate, ExprContext *econtext,
slot = econtext->ecxt_outertuple;
break;
- /* INDEX_VAR is handled by default case */
+ /* INDEX_VAR and CUSTOM_VAR are handled by default case */
default: /* get the tuple from the relation being
* scanned */
@@ -732,7 +732,7 @@ ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate, ExprContext *econtext,
slot = econtext->ecxt_outertuple;
break;
- /* INDEX_VAR is handled by default case */
+ /* INDEX_VAR and CUSTOM_VAR are handled by default case */
default: /* get the tuple from the relation being
* scanned */
@@ -915,7 +915,7 @@ ExecEvalWholeRowFast(WholeRowVarExprState *wrvstate, ExprContext *econtext,
slot = econtext->ecxt_outertuple;
break;
- /* INDEX_VAR is handled by default case */
+ /* INDEX_VAR and CUSTOM_VAR are handled by default case */
default: /* get the tuple from the relation being
* scanned */
@@ -991,7 +991,7 @@ ExecEvalWholeRowSlow(WholeRowVarExprState *wrvstate, ExprContext *econtext,
slot = econtext->ecxt_outertuple;
break;
- /* INDEX_VAR is handled by default case */
+ /* INDEX_VAR and CUSTOM_VAR are handled by default case */
default: /* get the tuple from the relation being
* scanned */
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 39e3b2e..df0d295 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -578,7 +578,7 @@ ExecBuildProjectionInfo(List *targetList,
projInfo->pi_lastOuterVar = attnum;
break;
- /* INDEX_VAR is handled by default case */
+ /* INDEX_VAR and CUSTOM_VAR are handled by default case */
default:
varSlotOffsets[numSimpleVars] = offsetof(ExprContext,
@@ -638,7 +638,7 @@ get_last_attnums(Node *node, ProjectionInfo *projInfo)
projInfo->pi_lastOuterVar = attnum;
break;
- /* INDEX_VAR is handled by default case */
+ /* INDEX_VAR and CUSTOM_VAR are handled by default case */
default:
if (projInfo->pi_lastScanVar < attnum)
diff --git a/src/backend/executor/nodeCustom.c b/src/backend/executor/nodeCustom.c
new file mode 100644
index 0000000..61bca22
--- /dev/null
+++ b/src/backend/executor/nodeCustom.c
@@ -0,0 +1,252 @@
+/* ------------------------------------------------------------------------
+ *
+ * nodeCustom.c
+ * Routines to handle execution of custom plan, scan and join node
+ *
+ * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * ------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "executor/nodeCustom.h"
+#include "parser/parsetree.h"
+#include "utils/hsearch.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+
+/* static variables */
+static HTAB *custom_provider_hash = NULL;
+
+/*
+ * register_custom_provider
+ *
+ * It registers a custom execution provider; that consists of a set of
+ * callbacks and is identified with a unique name.
+ */
+void
+register_custom_provider(const CustomProvider *provider)
+{
+ CustomProvider *entry;
+ bool found;
+
+ if (!custom_provider_hash)
+ {
+ HASHCTL ctl;
+
+ memset(&ctl, 0, sizeof(ctl));
+ ctl.hcxt = CacheMemoryContext;
+ ctl.keysize = NAMEDATALEN;
+ ctl.entrysize = sizeof(CustomProvider);
+
+ custom_provider_hash = hash_create("custom execution providers",
+ 32,
+ &ctl,
+ HASH_ELEM | HASH_CONTEXT);
+ }
+
+ entry = hash_search(custom_provider_hash,
+ provider->name,
+ HASH_ENTER, &found);
+ if (found)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("duplicate custom execution provider \"%s\"",
+ provider->name)));
+
+ Assert(strcmp(provider->name, entry->name) == 0);
+ memcpy(entry, provider, sizeof(CustomProvider));
+}
+
+/*
+ * get_custom_provider
+ *
+ * It finds a registered custom execution provide by its name
+ */
+CustomProvider *
+get_custom_provider(const char *custom_name)
+{
+ CustomProvider *entry;
+
+ /* lookup custom execution provider */
+ if (!custom_provider_hash)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("no custom execution provider was registered")));
+
+ entry = (CustomProvider *) hash_search(custom_provider_hash,
+ custom_name, HASH_FIND, NULL);
+ if (!entry)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("custom execution provider \"%s\" was not registered",
+ custom_name)));
+
+ return entry;
+}
+
+/*
+ * ExecInitCustomScan
+ *
+ * Allocation of CustomScanState and various initialization stuff.
+ * Note that some of initialization jobs are skipped if scanrelid is zero
+ * (that means this custom scan plan is not associated with a particular
+ * relation in range-table list.)
+ */
+CustomScanState *
+ExecInitCustomScan(CustomScan *node, EState *estate, int eflags)
+{
+ CustomProvider *provider = get_custom_provider(node->custom_name);
+ CustomScanState *csstate;
+ Plan *plan = &node->scan.plan;
+ Index scanrelid = node->scan.scanrelid;
+
+ /*
+ * Create state structure
+ */
+ csstate = makeNode(CustomScanState);
+ csstate->ss.ps.plan = plan;
+ csstate->ss.ps.state = estate;
+ csstate->custom_provider = provider;
+ csstate->custom_flags = node->custom_flags;
+ csstate->custom_state = NULL;
+
+ /*
+ * Miscellaneous initialization
+ */
+ ExecAssignExprContext(estate, &csstate->ss.ps);
+
+ /*
+ * Initialization of child expressions
+ */
+ csstate->ss.ps.targetlist =
+ (List *) ExecInitExpr((Expr *) plan->targetlist, &csstate->ss.ps);
+ csstate->ss.ps.qual =
+ (List *) ExecInitExpr((Expr *) plan->qual, &csstate->ss.ps);
+
+ /*
+ * tuple table initialization
+ *
+ * Note that ss_ScanTupleSlot is set only when scanrelid is associated
+ * with a particular relation. Elsewhere, it needs to be initialized by
+ * custom-scan provider itself if it internally uses ss_ScanTupleSlot.
+ * If it replaces varno of Var node by CUSTOM_VAR, it has to be set to
+ * reference underlying attribute name to generate EXPLAIN output.
+ */
+ ExecInitResultTupleSlot(estate, &csstate->ss.ps);
+ if (scanrelid > 0)
+ ExecInitScanTupleSlot(estate, &csstate->ss);
+
+ /*
+ * open the base relation and acquire appropriate lock on it,
+ * if this custom scan is connected with a particular relaion.
+ * Also, assign its scan type according to the table definition.
+ */
+ if (scanrelid > 0)
+ {
+ Relation rel = ExecOpenScanRelation(estate, scanrelid, eflags);
+
+ csstate->ss.ss_currentRelation = rel;
+ ExecAssignScanType(&csstate->ss, RelationGetDescr(rel));
+
+ csstate->ss.ps.ps_TupFromTlist = false;
+ }
+
+ /*
+ * Initialize result tuple type and projection info.
+ */
+ ExecAssignResultTypeFromTL(&csstate->ss.ps);
+
+ if (scanrelid > 0)
+ ExecAssignScanProjectionInfo(&csstate->ss);
+ else
+ ExecAssignProjectionInfo(&csstate->ss.ps, NULL);
+
+ /*
+ * Final initialization based on callback of BeginCustomScan method.
+ * Extension may be able to override initialization stuff above, if
+ * needed.
+ */
+ csstate->custom_provider->BeginCustomScan(csstate, eflags);
+
+ return csstate;
+}
+
+/*
+ * ExecCustomScan
+ *
+ * Just an entrypoint of ExecCustomScan method. All the stuff to fetch
+ * a tuple is a job of custom-scan provider.
+ */
+TupleTableSlot *
+ExecCustomScan(CustomScanState *csstate)
+{
+ return csstate->custom_provider->ExecCustomScan(csstate);
+}
+
+/*
+ * MultiExecCustomScan
+ *
+ * Aldo, just an entrypoint of MultiExecCustomScan method. All the stuff
+ * to fetch multiple tuples (according to expectation of upper node) is
+ * a job of custom-scan provider.
+ */
+Node *
+MultiExecCustomScan(CustomScanState *csstate)
+{
+ return csstate->custom_provider->MultiExecCustomScan(csstate);
+}
+
+/*
+ * ExecEndCustomScan
+ *
+ * It releases all the resources allocated on this scan.
+ */
+void
+ExecEndCustomScan(CustomScanState *csstate)
+{
+ /* Let the custom-exec shut down */
+ csstate->custom_provider->EndCustomScan(csstate);
+
+ /* Free the exprcontext */
+ ExecFreeExprContext(&csstate->ss.ps);
+
+ /* Clean out the tuple table, if exists */
+ ExecClearTuple(csstate->ss.ps.ps_ResultTupleSlot);
+ if (csstate->ss.ss_ScanTupleSlot)
+ ExecClearTuple(csstate->ss.ss_ScanTupleSlot);
+
+ /* close the relation, if opened */
+ if (csstate->ss.ss_currentRelation)
+ ExecCloseScanRelation(csstate->ss.ss_currentRelation);
+}
+
+/*
+ * ExecReScanCustomScan
+ */
+void
+ExecReScanCustomScan(CustomScanState *csstate)
+{
+ csstate->custom_provider->ReScanCustomScan(csstate);
+}
+
+/*
+ * ExecCustomMarkPos
+ */
+void
+ExecCustomMarkPos(CustomScanState *csstate)
+{
+ Assert((csstate->custom_flags & CUSTOM__SUPPORT_MARK_RESTORE) != 0);
+ csstate->custom_provider->ExecMarkPosCustomScan(csstate);
+}
+
+/*
+ * ExecCustomRestrPos
+ */
+void
+ExecCustomRestrPos(CustomScanState *csstate)
+{
+ Assert((csstate->custom_flags & CUSTOM__SUPPORT_MARK_RESTORE) != 0);
+ csstate->custom_provider->ExecRestorePosCustom(csstate);
+}
diff --git a/src/backend/nodes/bitmapset.c b/src/backend/nodes/bitmapset.c
index 540db16..44f2236 100644
--- a/src/backend/nodes/bitmapset.c
+++ b/src/backend/nodes/bitmapset.c
@@ -865,3 +865,65 @@ bms_hash_value(const Bitmapset *a)
return DatumGetUInt32(hash_any((const unsigned char *) a->words,
(lastword + 1) * sizeof(bitmapword)));
}
+
+/*
+ * bms_to_string / bms_from_string - transform bitmapset to/from text
+ * representation for portability purpose.
+ */
+char *
+bms_to_string(Bitmapset *a)
+{
+ char *result;
+ char *pos;
+ int i;
+
+ if (bms_is_empty(a))
+ return NULL;
+
+ result = palloc(a->nwords * (BITS_PER_BITMAPWORD / 4) + 1);
+ for (i = a->nwords, pos = result; i > 0; i--)
+ pos += sprintf(pos, "%08x", a->words[i - 1]);
+
+ return result;
+}
+
+Bitmapset *
+bms_from_string(const char *a)
+{
+ Bitmapset *result;
+ Size len;
+ int nwords;
+ int i, offset = 0;
+
+ if (a == NULL)
+ return NULL;
+
+ len = strlen(a);
+ if (len % (BITS_PER_BITMAPWORD / 4) != 0)
+ elog(WARNING, "strange bitmapset text representation: %s", a);
+
+ nwords = (len + BITS_PER_BITMAPWORD / 4 - 1) / (BITS_PER_BITMAPWORD / 4);
+ result = palloc(BITMAPSET_SIZE(nwords));
+ result->nwords = nwords;
+
+ for (i=result->nwords; i > 0; i--)
+ {
+ bitmapword word = 0;
+
+ do {
+ int c = a[offset++];
+ if (c >= '0' && c <= '9')
+ word = (word << 4) | (c - '0');
+ else if (c >= 'a' && c <= 'f')
+ word = (word << 4) | (c - 'a');
+ else if (c >= 'A' && c <= 'F')
+ word = (word << 4) | (c - 'A');
+ else
+ elog(ERROR, "invalid hexadecimal digit");
+ } while ((len - offset) % (BITS_PER_BITMAPWORD / 4) != 0);
+
+ result->words[i - 1] = word;
+ }
+
+ return result;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 65f3b98..b56af14 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -602,6 +602,33 @@ _copyForeignScan(const ForeignScan *from)
}
/*
+ * _copyCustomScan
+ */
+static CustomScan *
+_copyCustomScan(const CustomScan *from)
+{
+ CustomScan *newnode = makeNode(CustomScan);
+
+ /*
+ * copy node superclass fields
+ */
+ CopyScanFields((const Scan *) from, (Scan *) newnode);
+
+ /*
+ * copy remainder of node
+ */
+ COPY_STRING_FIELD(custom_name);
+ COPY_SCALAR_FIELD(custom_flags);
+ COPY_NODE_FIELD(custom_private);
+ COPY_NODE_FIELD(custom_exprs);
+
+ COPY_NODE_FIELD(subqry_plan);
+ COPY_NODE_FIELD(funcexpr);
+
+ return newnode;
+}
+
+/*
* CopyJoinFields
*
* This function copies the fields of the Join node. It is used by
@@ -3929,6 +3956,9 @@ copyObject(const void *from)
case T_ForeignScan:
retval = _copyForeignScan(from);
break;
+ case T_CustomScan:
+ retval = _copyCustomScan(from);
+ break;
case T_Join:
retval = _copyJoin(from);
break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 817b149..031112b 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -568,6 +568,22 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
}
static void
+_outCustomScan(StringInfo str, const CustomScan *node)
+{
+ WRITE_NODE_TYPE("CUSTOMSCAN");
+
+ _outScanInfo(str, (const Scan *) node);
+
+ WRITE_STRING_FIELD(custom_name);
+ WRITE_INT_FIELD(custom_flags);
+ WRITE_NODE_FIELD(custom_private);
+ WRITE_NODE_FIELD(custom_exprs);
+
+ WRITE_NODE_FIELD(subqry_plan);
+ WRITE_NODE_FIELD(funcexpr);
+}
+
+static void
_outJoin(StringInfo str, const Join *node)
{
WRITE_NODE_TYPE("JOIN");
@@ -2814,6 +2830,9 @@ _outNode(StringInfo str, const void *obj)
case T_ForeignScan:
_outForeignScan(str, obj);
break;
+ case T_CustomScan:
+ _outCustomScan(str, obj);
+ break;
case T_Join:
_outJoin(str, obj);
break;
diff --git a/src/backend/nodes/print.c b/src/backend/nodes/print.c
index 48ef325..29fcba9 100644
--- a/src/backend/nodes/print.c
+++ b/src/backend/nodes/print.c
@@ -333,6 +333,10 @@ print_expr(const Node *expr, const List *rtable)
relname = "INDEX";
attname = "?";
break;
+ case CUSTOM_VAR:
+ relname = "CUSTOM";
+ attname = "?";
+ break;
default:
{
RangeTblEntry *rte;
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index bfd3809..9d0cbf5 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -46,6 +46,8 @@ int geqo_threshold;
/* Hook for plugins to replace standard_join_search() */
join_search_hook_type join_search_hook = NULL;
+/* Hook for plugins to add custom scan paths */
+add_scan_path_hook_type add_scan_path_hook = NULL;
static void set_base_rel_sizes(PlannerInfo *root);
static void set_base_rel_pathlists(PlannerInfo *root);
@@ -399,6 +401,9 @@ set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
/* Consider TID scans */
create_tidscan_paths(root, rel);
+ /* Consider Custom scans */
+ add_custom_scan_paths(root,rel,rte);
+
/* Now find the cheapest of the paths for this rel */
set_cheapest(rel);
}
@@ -427,6 +432,9 @@ 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);
+ /* Consider Custom scans */
+ add_custom_scan_paths(root,rel,rte);
+
/* Select cheapest path */
set_cheapest(rel);
}
@@ -1246,6 +1254,9 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
/* Generate appropriate path */
add_path(rel, create_subqueryscan_path(root, rel, pathkeys, required_outer));
+ /* Consider Custom scans */
+ add_custom_scan_paths(root,rel,rte);
+
/* Select cheapest path (pretty easy in this case...) */
set_cheapest(rel);
}
@@ -1269,6 +1280,9 @@ set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
/* Generate appropriate path */
add_path(rel, create_functionscan_path(root, rel, required_outer));
+ /* Consider Custom scans */
+ add_custom_scan_paths(root,rel,rte);
+
/* Select cheapest path (pretty easy in this case...) */
set_cheapest(rel);
}
@@ -1292,6 +1306,9 @@ set_values_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
/* Generate appropriate path */
add_path(rel, create_valuesscan_path(root, rel, required_outer));
+ /* Consider Custom scans */
+ add_custom_scan_paths(root,rel,rte);
+
/* Select cheapest path (pretty easy in this case...) */
set_cheapest(rel);
}
@@ -1361,6 +1378,9 @@ set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
/* Generate appropriate path */
add_path(rel, create_ctescan_path(root, rel, required_outer));
+ /* Consider Custom scans */
+ add_custom_scan_paths(root,rel,rte);
+
/* Select cheapest path (pretty easy in this case...) */
set_cheapest(rel);
}
@@ -1414,6 +1434,9 @@ set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
/* Generate appropriate path */
add_path(rel, create_worktablescan_path(root, rel, required_outer));
+ /* Consider Custom scans */
+ add_custom_scan_paths(root,rel,rte);
+
/* 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 e7f8cec..c6e1634 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -130,9 +130,6 @@ static MergeScanSelCache *cached_scansel(PlannerInfo *root,
static void cost_rescan(PlannerInfo *root, Path *path,
Cost *rescan_startup_cost, Cost *rescan_total_cost);
static bool cost_qual_eval_walker(Node *node, cost_qual_eval_context *context);
-static void get_restriction_qual_cost(PlannerInfo *root, RelOptInfo *baserel,
- ParamPathInfo *param_info,
- QualCost *qpqual_cost);
static bool has_indexed_join_quals(NestPath *joinpath);
static double approx_tuple_count(PlannerInfo *root, JoinPath *path,
List *quals);
@@ -2312,7 +2309,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;
/*
@@ -3201,7 +3198,7 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
* some of the quals. We assume baserestrictcost was previously set by
* set_baserel_size_estimates().
*/
-static void
+void
get_restriction_qual_cost(PlannerInfo *root, RelOptInfo *baserel,
ParamPathInfo *param_info,
QualCost *qpqual_cost)
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 5b477e5..9483614 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -21,6 +21,8 @@
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
+/* Hook for plugins to add custom join paths */
+add_join_path_hook_type add_join_path_hook = NULL;
#define PATH_PARAM_BY_REL(path, rel) \
((path)->param_info && bms_overlap(PATH_REQ_OUTER(path), (rel)->relids))
@@ -259,6 +261,22 @@ add_paths_to_joinrel(PlannerInfo *root,
restrictlist, jointype,
sjinfo, &semifactors,
param_source_rels, extra_lateral_rels);
+
+ /*
+ * 5. Also consider paths being provided with custom execution provider.
+ */
+ if (add_join_path_hook)
+ (*add_join_path_hook)(root,
+ joinrel,
+ outerrel,
+ innerrel,
+ jointype,
+ sjinfo,
+ restrictlist,
+ mergeclause_list,
+ &semifactors,
+ param_source_rels,
+ extra_lateral_rels);
}
/*
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 9b9eb2f..9626d08 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -21,6 +21,7 @@
#include "access/skey.h"
#include "catalog/pg_class.h"
+#include "executor/nodeCustom.h"
#include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -77,6 +78,9 @@ static WorkTableScan *create_worktablescan_plan(PlannerInfo *root, Path *best_pa
List *tlist, List *scan_clauses);
static ForeignScan *create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
List *tlist, List *scan_clauses);
+static CustomScan *create_customscan_plan(PlannerInfo *root,
+ CustomPath *best_path,
+ List *tlist, List *scan_clauses);
static NestLoop *create_nestloop_plan(PlannerInfo *root, NestPath *best_path,
Plan *outer_plan, Plan *inner_plan);
static MergeJoin *create_mergejoin_plan(PlannerInfo *root, MergePath *best_path,
@@ -235,6 +239,7 @@ create_plan_recurse(PlannerInfo *root, Path *best_path)
case T_CteScan:
case T_WorkTableScan:
case T_ForeignScan:
+ case T_CustomScan:
plan = create_scan_plan(root, best_path);
break;
case T_HashJoin:
@@ -411,6 +416,13 @@ create_scan_plan(PlannerInfo *root, Path *best_path)
scan_clauses);
break;
+ case T_CustomScan:
+ plan = (Plan *) create_customscan_plan(root,
+ (CustomPath *) best_path,
+ tlist,
+ scan_clauses);
+ break;
+
default:
elog(ERROR, "unrecognized node type: %d",
(int) best_path->pathtype);
@@ -2016,6 +2028,97 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
return scan_plan;
}
+/*
+ * create_customscan_plan
+ * Returns a custom-scan plan for the base relation scanned by 'best_path'
+ * with restriction clauses 'scan_clauses' and targetlist 'tlist'.
+ */
+static CustomScan *
+create_customscan_plan(PlannerInfo *root,
+ CustomPath *best_path,
+ List *tlist,
+ List *scan_clauses)
+{
+ CustomProvider *provider = get_custom_provider(best_path->custom_name);
+ CustomScan *scan_plan = makeNode(CustomScan);
+ RelOptKind reloptkind = best_path->path.parent->reloptkind;
+ RangeTblEntry *rte;
+ Index scan_relid;
+
+ if (reloptkind == RELOPT_BASEREL ||
+ reloptkind == RELOPT_OTHER_MEMBER_REL)
+ {
+ scan_relid = best_path->path.parent->relid;
+
+ rte = planner_rt_fetch(scan_relid, root);
+ /*
+ * For EXPLAIN output, we save various information in CustomScan plan
+ * structure. Custom-scan provider can utilize them, but it is not
+ * recommendablt to adjust.
+ */
+ if (rte->rtekind == RTE_SUBQUERY)
+ {
+ if (best_path->path.param_info)
+ {
+ List *subplan_params
+ = best_path->path.parent->subplan_params;
+ process_subquery_nestloop_params(root, subplan_params);
+ }
+ scan_plan->subqry_plan = best_path->path.parent->subplan;
+ }
+ else if (rte->rtekind == RTE_FUNCTION)
+ {
+ Node *funcexpr = rte->funcexpr;
+
+ if (best_path->path.param_info)
+ funcexpr = replace_nestloop_params(root, funcexpr);
+ scan_plan->funcexpr = funcexpr;
+ }
+ }
+ else if (reloptkind == RELOPT_JOINREL)
+ scan_relid = 0;
+ else
+ elog(ERROR, "unexpected reloptkind: %d", (int)reloptkind);
+
+ scan_clauses = order_qual_clauses(root, scan_clauses);
+ scan_plan->scan.plan.targetlist = NULL; /* to be set by callback */
+ scan_plan->scan.plan.qual = NULL; /* to be set by callback */
+ scan_plan->scan.plan.lefttree = NULL;
+ scan_plan->scan.plan.righttree = NULL;
+ scan_plan->scan.scanrelid = scan_relid;
+
+ scan_plan->custom_name = pstrdup(best_path->custom_name);
+ scan_plan->custom_flags = best_path->custom_flags;
+ scan_plan->custom_private = NIL;
+ scan_plan->custom_exprs = NULL;
+
+ /*
+ * Let custom scan provider perform to set up this custom-scan plan
+ * according to the given path information.
+ */
+ provider->InitCustomScanPlan(root, scan_plan,
+ best_path, tlist, scan_clauses);
+
+ /* Copy cost data from Path to Plan; no need to make callback do this */
+ copy_path_costsize(&scan_plan->scan.plan, &best_path->path);
+
+ /*
+ * Replace any outer-relation variables with nestloop params in the qual
+ * and custom_exprs expressions. We do this last so that the FDW doesn't
+ * have to be involved. (Note that parts of custom_exprs could have come
+ * from join clauses, so doing this beforehand on the scan_clauses
+ * wouldn't work.)
+ */
+ if (best_path->path.param_info)
+ {
+ scan_plan->scan.plan.qual = (List *)
+ replace_nestloop_params(root, (Node *) scan_plan->scan.plan.qual);
+ scan_plan->custom_exprs = (List *)
+ replace_nestloop_params(root, (Node *) scan_plan->custom_exprs);
+ }
+
+ return scan_plan;
+}
/*****************************************************************************
*
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index b78d727..30cf7e5 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -17,6 +17,7 @@
#include "access/transam.h"
#include "catalog/pg_type.h"
+#include "executor/nodeCustom.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/pathnode.h"
@@ -578,6 +579,30 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
}
break;
+ case T_CustomScan:
+ {
+ CustomScan *splan = (CustomScan *) plan;
+ CustomProvider *provider
+ = get_custom_provider(splan->custom_name);
+
+ if (provider->SetPlanRefCustomScan)
+ provider->SetPlanRefCustomScan(root, splan, rtoffset);
+ else if (splan->scan.scanrelid > 0)
+ {
+ splan->scan.scanrelid += rtoffset;
+ splan->scan.plan.targetlist =
+ fix_scan_list(root, splan->scan.plan.targetlist,
+ rtoffset);
+ splan->scan.plan.qual =
+ fix_scan_list(root, splan->scan.plan.qual, rtoffset);
+ splan->custom_exprs =
+ fix_scan_list(root, splan->custom_exprs, rtoffset);
+ }
+ else
+ elog(ERROR, "No implementation to set plan reference");
+ }
+ break;
+
case T_NestLoop:
case T_MergeJoin:
case T_HashJoin:
@@ -1059,7 +1084,7 @@ copyVar(Var *var)
* We assume it's okay to update opcode info in-place. So this could possibly
* scribble on the planner's input data structures, but it's OK.
*/
-static void
+void
fix_expr_common(PlannerInfo *root, Node *node)
{
/* We assume callers won't call us on a NULL pointer */
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 0df70c4..644a532 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -2194,6 +2194,16 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params,
context.paramids = bms_add_members(context.paramids, scan_params);
break;
+ case T_CustomScan:
+ finalize_primnode((Node *) ((CustomScan *) plan)->custom_exprs,
+ &context);
+ context.paramids = bms_add_members(context.paramids, scan_params);
+ /*
+ * XXX - Is it sufficient to do? Don't we need something special
+ * if CustomScan override FunctionScan or SubqueryScan.
+ */
+ 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 64b17051..46e814d 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -1738,6 +1738,46 @@ create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel,
}
/*
+ * create_customscan_path
+ * Creates a path corresponding to a scan of a relation based on logic
+ * logic being provided by extensions.
+ *
+ * This function is never called from core PostgreSQL. An usual usage is
+ * invocation from callbacks on add_scan_path_hook. We don't have any
+ * assumption on the custom scan logic, thus, caller is responsible to
+ * set adequate cost estimation here.
+ */
+CustomPath *
+create_customscan_path(PlannerInfo *root,
+ RelOptInfo *baserel,
+ double rows,
+ Cost startup_cost,
+ Cost total_cost,
+ List *pathkeys,
+ Relids required_outer,
+ const char *custom_name,
+ uint32 custom_flags,
+ List *custom_private)
+{
+ CustomPath *pathnode = makeNode(CustomPath);
+
+ pathnode->path.pathtype = T_CustomScan;
+ pathnode->path.parent = baserel;
+ pathnode->path.param_info = get_baserel_parampathinfo(root, baserel,
+ required_outer);
+ pathnode->path.rows = rows;
+ pathnode->path.startup_cost = startup_cost;
+ pathnode->path.total_cost = total_cost;
+ pathnode->path.pathkeys = pathkeys;
+
+ pathnode->custom_name = pstrdup(custom_name);
+ pathnode->custom_flags = custom_flags;
+ pathnode->custom_private = custom_private;
+
+ return pathnode;
+}
+
+/*
* calc_nestloop_required_outer
* Compute the required_outer set for a nestloop join path
*
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 04b1c4f..eda53d6 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -143,6 +143,7 @@ typedef struct
List *outer_tlist; /* referent for OUTER_VAR Vars */
List *inner_tlist; /* referent for INNER_VAR Vars */
List *index_tlist; /* referent for INDEX_VAR Vars */
+ TupleDesc custom_tupdesc; /* referent for CUSTOM_VAR Vars */
} deparse_namespace;
/*
@@ -2362,14 +2363,19 @@ deparse_context_for(const char *aliasname, Oid relid)
* deparse_context_for_planstate - Build deparse context for a plan
*
* When deparsing an expression in a Plan tree, we might have to resolve
- * OUTER_VAR, INNER_VAR, or INDEX_VAR references. To do this, the caller must
- * provide the parent PlanState node. Then OUTER_VAR and INNER_VAR references
- * can be resolved by drilling down into the left and right child plans.
+ * special varno (OUTER_VAR, INNER_VAR, INDEX_VAR or CUSTOM_VAR) references.
+ * To do this, the caller must provide the parent PlanState node. Then
+ * OUTER_VAR and INNER_VAR references can be resolved by drilling down into
+ * the left and right child plans.
* Similarly, INDEX_VAR references can be resolved by reference to the
* indextlist given in the parent IndexOnlyScan node. (Note that we don't
* currently support deparsing of indexquals in regular IndexScan or
* BitmapIndexScan nodes; for those, we can only deparse the indexqualorig
* fields, which won't contain INDEX_VAR Vars.)
+ * Also, CUSTOM_VAR references can be resolved by reference to the TupleDesc
+ * of ss_ScanTupleSlot in CustomScanState node. (Note that custom scan
+ * provider must be responsible to initialize the ss_ScanTupleSlot with
+ * appropriate TupleDesc; being likely constructed by ExecTypeFromTL).
*
* Note: planstate really ought to be declared as "PlanState *", but we use
* "Node *" to avoid having to include execnodes.h in builtins.h.
@@ -3627,6 +3633,14 @@ set_deparse_planstate(deparse_namespace *dpns, PlanState *ps)
dpns->index_tlist = ((IndexOnlyScan *) ps->plan)->indextlist;
else
dpns->index_tlist = NIL;
+
+ /* custom_tupdesc is set only if it's an CustomScan */
+ if (IsA(ps, CustomScanState) &&
+ ((CustomScanState *)ps)->ss.ss_ScanTupleSlot)
+ dpns->custom_tupdesc =
+ ((CustomScanState *)ps)->ss.ss_ScanTupleSlot->tts_tupleDescriptor;
+ else
+ dpns->custom_tupdesc = NULL;
}
/*
@@ -5294,6 +5308,18 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
return NULL;
}
+ else if (var->varno == CUSTOM_VAR && dpns->custom_tupdesc)
+ {
+ TupleDesc tupdesc = dpns->custom_tupdesc;
+
+ Assert(netlevelsup == 0);
+ Assert(var->varattno > 0 && var->varattno <= tupdesc->natts);
+
+ attname = NameStr(tupdesc->attrs[var->varattno - 1]->attname);
+ appendStringInfoString(buf, quote_identifier(attname));
+
+ return attname;
+ }
else
{
elog(ERROR, "bogus varno: %d", var->varno);
@@ -5564,6 +5590,18 @@ get_name_for_var_field(Var *var, int fieldno,
return result;
}
+ else if (var->varno == CUSTOM_VAR && dpns->custom_tupdesc)
+ {
+ TupleDesc tupdesc = dpns->custom_tupdesc;
+ const char *result;
+
+ Assert(netlevelsup == 0);
+ Assert(var->varattno > 0 && var->varattno <= tupdesc->natts);
+
+ result = NameStr(tupdesc->attrs[var->varattno - 1]->attname);
+
+ return result;
+ }
else
{
elog(ERROR, "bogus varno: %d", var->varno);
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index 0350ef6..0c7a233 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -159,15 +159,19 @@ DESCR("equal");
#define TIDEqualOperator 387
DATA(insert OID = 402 ( "<>" PGNSP PGUID b f f 27 27 16 402 387 tidne neqsel neqjoinsel ));
DESCR("not equal");
+#define TIDNotEqualOperator 402
DATA(insert OID = 2799 ( "<" PGNSP PGUID b f f 27 27 16 2800 2802 tidlt scalarltsel scalarltjoinsel ));
DESCR("less than");
#define TIDLessOperator 2799
DATA(insert OID = 2800 ( ">" PGNSP PGUID b f f 27 27 16 2799 2801 tidgt scalargtsel scalargtjoinsel ));
DESCR("greater than");
+#define TIDGreaterOperator 2800
DATA(insert OID = 2801 ( "<=" PGNSP PGUID b f f 27 27 16 2802 2800 tidle scalarltsel scalarltjoinsel ));
DESCR("less than or equal");
+#define TIDLessEqualOperator 2801
DATA(insert OID = 2802 ( ">=" PGNSP PGUID b f f 27 27 16 2801 2799 tidge scalargtsel scalargtjoinsel ));
DESCR("greater than or equal");
+#define TIDGreaterEqualOperator 2802
DATA(insert OID = 410 ( "=" PGNSP PGUID b t t 20 20 16 410 411 int8eq eqsel eqjoinsel ));
DESCR("equal");
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 75841c8..51537d2 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -16,6 +16,7 @@
#include "executor/execdesc.h"
#include "nodes/parsenodes.h"
+#include "nodes/relation.h"
/*
@@ -102,7 +103,7 @@ extern PGDLLIMPORT ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook;
extern void ExecReScan(PlanState *node);
extern void ExecMarkPos(PlanState *node);
extern void ExecRestrPos(PlanState *node);
-extern bool ExecSupportsMarkRestore(NodeTag plantype);
+extern bool ExecSupportsMarkRestore(Path *path);
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..8ea5693
--- /dev/null
+++ b/src/include/executor/nodeCustom.h
@@ -0,0 +1,94 @@
+/* ------------------------------------------------------------------------
+ *
+ * nodeCustom.h
+ *
+ * prototypes for CustomScan nodes
+ *
+ * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * ------------------------------------------------------------------------
+ */
+#ifndef NODECUSTOM_H
+#define NODECUSTOM_H
+#include "commands/explain.h"
+#include "nodes/plannodes.h"
+#include "nodes/relation.h"
+
+/*
+ * Definition of the custom execution provider callbacks
+ */
+typedef void (*InitCustomScanPlan_function)(PlannerInfo *root,
+ CustomScan *cscan_plan,
+ CustomPath *cscan_path,
+ List *tlist,
+ List *scan_clauses);
+typedef void (*SetPlanRefCustomScan_function)(PlannerInfo *root,
+ CustomScan *cscan_plan,
+ int rtoffset);
+typedef void (*BeginCustomScan_function)(CustomScanState *csstate, int eflags);
+typedef TupleTableSlot *(*ExecCustomScan_function)(CustomScanState *csstate);
+typedef Node *(*MultiExecCustomScan_function)(CustomScanState *csstate);
+typedef void (*EndCustomScan_function)(CustomScanState *csstate);
+
+typedef void (*ReScanCustomScan_function)(CustomScanState *csstate);
+typedef void (*ExecMarkPosCustomScan_function)(CustomScanState *csstate);
+typedef void (*ExecRestorePosCustom_function)(CustomScanState *csstate);
+
+typedef void (*ExplainCustomScan_function)(CustomScanState *csstate,
+ ExplainState *es);
+
+typedef struct CustomProvider
+{
+ char name[NAMEDATALEN];
+
+ InitCustomScanPlan_function InitCustomScanPlan;
+ SetPlanRefCustomScan_function SetPlanRefCustomScan;
+
+ BeginCustomScan_function BeginCustomScan;
+ ExecCustomScan_function ExecCustomScan;
+ MultiExecCustomScan_function MultiExecCustomScan;
+ EndCustomScan_function EndCustomScan;
+
+ ReScanCustomScan_function ReScanCustomScan;
+ ExecMarkPosCustomScan_function ExecMarkPosCustomScan;
+ ExecRestorePosCustom_function ExecRestorePosCustom;
+
+ ExplainCustomScan_function ExplainCustomScan;
+} CustomProvider;
+
+/* Flags of CustomScan */
+
+/*
+ * CUSTOM__SUPPORT_MARK_RESTORE informs optimizer this custom scan provider
+ * support ExecCustomMarkPos and ExecCustomRestrPos callbacks.
+ */
+#define CUSTOM__SUPPORT_MARK_RESTORE 0x0001
+
+/*
+ * CUSTOM__SUPPORT_BACKWARD_SCAN informs optimizer this custom scan provider
+ * is designed to support backward scan.
+ */
+#define CUSTOM__SUPPORT_BACKWARD_SCAN 0x0002
+
+/*
+ * Registration and lookup custom execution provider
+ */
+extern void register_custom_provider(const CustomProvider *provider);
+
+extern CustomProvider *get_custom_provider(const char *custom_name);
+
+/*
+ * General executor code
+ */
+extern CustomScanState *ExecInitCustomScan(CustomScan *csstate,
+ EState *estate, int eflags);
+extern TupleTableSlot *ExecCustomScan(CustomScanState *csstate);
+extern Node *MultiExecCustomScan(CustomScanState *csstate);
+extern void ExecEndCustomScan(CustomScanState *csstate);
+
+extern void ExecReScanCustomScan(CustomScanState *csstate);
+extern void ExecCustomMarkPos(CustomScanState *csstate);
+extern void ExecCustomRestrPos(CustomScanState *csstate);
+
+#endif /* NODECUSTOM_H */
diff --git a/src/include/nodes/bitmapset.h b/src/include/nodes/bitmapset.h
index 2a4b41d..73424f5 100644
--- a/src/include/nodes/bitmapset.h
+++ b/src/include/nodes/bitmapset.h
@@ -93,4 +93,8 @@ extern int bms_first_member(Bitmapset *a);
/* support for hashtables using Bitmapsets as keys: */
extern uint32 bms_hash_value(const Bitmapset *a);
+/* support for string representation */
+extern char *bms_to_string(Bitmapset *a);
+extern Bitmapset *bms_from_string(const char *a);
+
#endif /* BITMAPSET_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 3b430e0..db4176c 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -1494,6 +1494,23 @@ typedef struct ForeignScanState
void *fdw_state; /* foreign-data wrapper can keep state here */
} ForeignScanState;
+/* ----------------
+ * CustomScanState information
+ *
+ * CustomScan nodes are used to scan various relations using custom
+ * logic.
+ * ----------------
+ */
+typedef struct CustomScanState
+{
+ ScanState ss;
+
+ /* use struct pointer to avoid including nodeCustom.h here */
+ struct CustomProvider *custom_provider;
+ int custom_flags;
+ void *custom_state;
+} CustomScanState;
+
/* ----------------------------------------------------------------
* Join State Information
* ----------------------------------------------------------------
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 78368c6..8f00a6b 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -62,6 +62,7 @@ typedef enum NodeTag
T_CteScan,
T_WorkTableScan,
T_ForeignScan,
+ T_CustomScan,
T_Join,
T_NestLoop,
T_MergeJoin,
@@ -107,6 +108,7 @@ typedef enum NodeTag
T_CteScanState,
T_WorkTableScanState,
T_ForeignScanState,
+ T_CustomScanState,
T_JoinState,
T_NestLoopState,
T_MergeJoinState,
@@ -224,6 +226,7 @@ typedef enum NodeTag
T_HashPath,
T_TidPath,
T_ForeignPath,
+ T_CustomPath,
T_AppendPath,
T_MergeAppendPath,
T_ResultPath,
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 44ea0b7..936591b 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -483,6 +483,22 @@ typedef struct ForeignScan
bool fsSystemCol; /* true if any "system column" is needed */
} ForeignScan;
+/* ----------------
+ * CustomScan node
+ * ----------------
+ */
+typedef struct CustomScan
+{
+ Scan scan;
+
+ const char *custom_name; /* name of custom scan provider */
+ int custom_flags; /* a set of CUSTOM__* flags */
+ List *custom_private; /* private data for CSP */
+ List *custom_exprs; /* expressions that CSP may execute */
+
+ Plan *subqry_plan; /* valid, if RTE_SUBQUERY */
+ Node *funcexpr; /* valid, if RTE_FUNCTION */
+} CustomScan;
/*
* ==========
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 7918537..b71c7ca 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -134,6 +134,7 @@ typedef struct Expr
#define INNER_VAR 65000 /* reference to inner subplan */
#define OUTER_VAR 65001 /* reference to outer subplan */
#define INDEX_VAR 65002 /* reference to index column */
+#define CUSTOM_VAR 65003 /* reference to custom column */
#define IS_SPECIAL_VARNO(varno) ((varno) >= INNER_VAR)
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index a2853fb..55fa8aa 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -867,6 +867,22 @@ typedef struct ForeignPath
} ForeignPath;
/*
+ * CustomPath represents a scan using custom logic
+ *
+ * custom_name is the identifier of custom scan provider when it was
+ * registered. custom_flags is a set of CUSTOM__* bits to control its
+ * behavior. custom_private allows extension to store its private data
+ * but has to be safe for copyObject().
+ */
+typedef struct CustomPath
+{
+ Path path;
+ const char *custom_name; /* name of custom scan provider */
+ int custom_flags; /* CUSTOM__* flags in nodeCustom.h */
+ List *custom_private; /* can be used for private data */
+} CustomPath;
+
+/*
* AppendPath represents an Append plan, ie, successive execution of
* several member plans.
*
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index 444ab740..a2873ec 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -145,6 +145,9 @@ extern void final_cost_hashjoin(PlannerInfo *root, HashPath *path,
extern void cost_subplan(PlannerInfo *root, SubPlan *subplan, Plan *plan);
extern void cost_qual_eval(QualCost *cost, List *quals, PlannerInfo *root);
extern void cost_qual_eval_node(QualCost *cost, Node *qual, PlannerInfo *root);
+extern void get_restriction_qual_cost(PlannerInfo *root, RelOptInfo *baserel,
+ ParamPathInfo *param_info,
+ QualCost *qpqual_cost);
extern void compute_semi_anti_join_factors(PlannerInfo *root,
RelOptInfo *outerrel,
RelOptInfo *innerrel,
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 9686229..1225970 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -82,6 +82,16 @@ extern ForeignPath *create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel,
List *pathkeys,
Relids required_outer,
List *fdw_private);
+extern CustomPath *create_customscan_path(PlannerInfo *root,
+ RelOptInfo *baserel,
+ double rows,
+ Cost startup_cost,
+ Cost total_cost,
+ List *pathkeys,
+ Relids required_outer,
+ const char *custom_name,
+ uint32 custom_flags,
+ List *custom_private);
extern Relids calc_nestloop_required_outer(Path *outer_path, Path *inner_path);
extern Relids calc_non_nestloop_required_outer(Path *outer_path, Path *inner_path);
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 9ef93c7..882baf6 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -29,6 +29,31 @@ typedef RelOptInfo *(*join_search_hook_type) (PlannerInfo *root,
List *initial_rels);
extern PGDLLIMPORT join_search_hook_type join_search_hook;
+/* Hook for plugins to add custom scan path, in addition to default ones */
+typedef void (*add_scan_path_hook_type)(PlannerInfo *root,
+ RelOptInfo *baserel,
+ RangeTblEntry *rte);
+extern PGDLLIMPORT add_scan_path_hook_type add_scan_path_hook;
+
+#define add_custom_scan_paths(root,baserel,rte) \
+ do { \
+ if (add_scan_path_hook) \
+ (*add_scan_path_hook)((root),(baserel),(rte)); \
+ } while(0)
+
+/* Hook for plugins to add custom join path, in addition to default ones */
+typedef void (*add_join_path_hook_type)(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ JoinType jointype,
+ SpecialJoinInfo *sjinfo,
+ List *restrictlist,
+ List *mergeclause_list,
+ SemiAntiJoinFactors *semifactors,
+ Relids param_source_rels,
+ Relids extra_lateral_rels);
+extern PGDLLIMPORT add_join_path_hook_type add_join_path_hook;
extern RelOptInfo *make_one_rel(PlannerInfo *root, List *joinlist);
extern RelOptInfo *standard_join_search(PlannerInfo *root, int levels_needed,
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index ba7ae7c..13cfba8 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -127,6 +127,7 @@ extern List *remove_useless_joins(PlannerInfo *root, List *joinlist);
* prototypes for plan/setrefs.c
*/
extern Plan *set_plan_references(PlannerInfo *root, Plan *plan);
+extern void fix_expr_common(PlannerInfo *root, Node *node);
extern void fix_opfuncids(Node *node);
extern void set_opfuncid(OpExpr *opexpr);
extern void set_sa_opfuncid(ScalarArrayOpExpr *opexpr);
diff --git a/src/test/regress/GNUmakefile b/src/test/regress/GNUmakefile
index d5935b6..9645025 100644
--- a/src/test/regress/GNUmakefile
+++ b/src/test/regress/GNUmakefile
@@ -90,6 +90,7 @@ regress_data_files = \
install-tests: all install install-lib installdirs-tests
$(MAKE) -C $(top_builddir)/contrib/spi install
+ $(MAKE) -C $(top_builddir)/contrib/ctidscan install
for file in $(regress_data_files); do \
$(INSTALL_DATA) $$file '$(DESTDIR)$(pkglibdir)/regress/'$$file || exit; \
done
@@ -98,9 +99,9 @@ installdirs-tests: installdirs
$(MKDIR_P) $(patsubst $(srcdir)/%/,'$(DESTDIR)$(pkglibdir)/regress/%',$(sort $(dir $(regress_data_files))))
-# Get some extra C modules from contrib/spi and contrib/dummy_seclabel...
+# Get some extra C modules from contrib/spi, dummy_seclabel and ctidscan
-all: refint$(DLSUFFIX) autoinc$(DLSUFFIX) dummy_seclabel$(DLSUFFIX)
+all: refint$(DLSUFFIX) autoinc$(DLSUFFIX) dummy_seclabel$(DLSUFFIX) ctidscan$(DLSUFFIX)
refint$(DLSUFFIX): $(top_builddir)/contrib/spi/refint$(DLSUFFIX)
cp $< $@
@@ -111,19 +112,27 @@ autoinc$(DLSUFFIX): $(top_builddir)/contrib/spi/autoinc$(DLSUFFIX)
dummy_seclabel$(DLSUFFIX): $(top_builddir)/contrib/dummy_seclabel/dummy_seclabel$(DLSUFFIX)
cp $< $@
+ctidscan$(DLSUFFIX): $(top_builddir)/contrib/ctidscan/ctidscan$(DLSUFFIX)
+ cp $< $@
+
$(top_builddir)/contrib/spi/refint$(DLSUFFIX): | submake-contrib-spi ;
$(top_builddir)/contrib/spi/autoinc$(DLSUFFIX): | submake-contrib-spi ;
$(top_builddir)/contrib/dummy_seclabel/dummy_seclabel$(DLSUFFIX): | submake-contrib-dummy_seclabel ;
+$(top_builddir)/contrib/ctidscan/ctidscan$(DLSUFFIX): | submake-contrib-ctidscan
+
submake-contrib-spi:
$(MAKE) -C $(top_builddir)/contrib/spi
submake-contrib-dummy_seclabel:
$(MAKE) -C $(top_builddir)/contrib/dummy_seclabel
-.PHONY: submake-contrib-spi submake-contrib-dummy_seclabel
+submake-contrib-ctidscan:
+ $(MAKE) -C $(top_builddir)/contrib/ctidscan
+
+.PHONY: submake-contrib-spi submake-contrib-dummy_seclabel submake-contrib-ctidscan
# Tablespace setup
diff --git a/src/test/regress/input/custom_scan.source b/src/test/regress/input/custom_scan.source
new file mode 100644
index 0000000..1ad0e7a
--- /dev/null
+++ b/src/test/regress/input/custom_scan.source
@@ -0,0 +1,49 @@
+--
+-- Regression Tests for Custom Scan APIs
+--
+
+-- construction of test data
+SET client_min_messages TO 'warning';
+
+CREATE SCHEMA regtest_custom_scan;
+
+SET search_path TO regtest_custom_scan, public;
+
+CREATE TABLE t1 (
+ a int primary key,
+ b text
+);
+INSERT INTO t1 (SELECT s, md5(s::text) FROM generate_series(1,400) s);
+VACUUM ANALYZE t1;
+
+CREATE TABLE t2 (
+ x int primary key,
+ y text
+);
+INSERT INTO t2 (SELECT s, md5(s::text)||md5(s::text) FROM generate_series(1,400) s);
+VACUUM ANALYZE t2;
+
+RESET client_min_messages;
+
+--
+-- Check Plans if no special extension is loaded.
+--
+EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE b like '%789%';
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid = '(2,10)'::tid;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+
+LOAD '@libdir@/ctidscan@DLSUFFIX@';
+EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE b like '%789%';
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid = '(2,10)'::tid;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+EXPLAIN (costs off) SELECT * FROM t1 JOIN t2 ON t1.ctid = t2.ctid WHERE t1.ctid < '(2,10)'::tid AND t2.ctid > '(1,75)'::tid;
+
+SELECT ctid,* FROM t1 WHERE ctid < '(1,20)'::tid;
+SELECT ctid,* FROM t1 WHERE ctid > '(4,0)'::tid;
+SELECT ctid,* FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+SELECT t1.ctid,* FROM t1 JOIN t2 ON t1.ctid = t2.ctid WHERE t1.ctid < '(2,10)'::tid AND t2.ctid > '(1,75)'::tid;
+
+-- Test creanup
+DROP SCHEMA regtest_custom_scan CASCADE;
\ No newline at end of file
diff --git a/src/test/regress/output/custom_scan.source b/src/test/regress/output/custom_scan.source
new file mode 100644
index 0000000..09c1bda
--- /dev/null
+++ b/src/test/regress/output/custom_scan.source
@@ -0,0 +1,290 @@
+--
+-- Regression Tests for Custom Scan APIs
+--
+-- construction of test data
+SET client_min_messages TO 'warning';
+CREATE SCHEMA regtest_custom_scan;
+SET search_path TO regtest_custom_scan, public;
+CREATE TABLE t1 (
+ a int primary key,
+ b text
+);
+INSERT INTO t1 (SELECT s, md5(s::text) FROM generate_series(1,400) s);
+VACUUM ANALYZE t1;
+CREATE TABLE t2 (
+ x int primary key,
+ y text
+);
+INSERT INTO t2 (SELECT s, md5(s::text)||md5(s::text) FROM generate_series(1,400) s);
+VACUUM ANALYZE t2;
+RESET client_min_messages;
+--
+-- Check Plans if no special extension is loaded.
+--
+EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40;
+ QUERY PLAN
+--------------------------------
+ Index Scan using t1_pkey on t1
+ Index Cond: (a = 40)
+(2 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE b like '%789%';
+ QUERY PLAN
+--------------------------------
+ Seq Scan on t1
+ Filter: (b ~~ '%789%'::text)
+(2 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid = '(2,10)'::tid;
+ QUERY PLAN
+------------------------------------
+ Tid Scan on t1
+ TID Cond: (ctid = '(2,10)'::tid)
+(2 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+ QUERY PLAN
+------------------------------------------------------------------
+ Seq Scan on t1
+ Filter: ((ctid >= '(2,115)'::tid) AND (ctid <= '(3,10)'::tid))
+(2 rows)
+
+LOAD '@libdir@/ctidscan@DLSUFFIX@';
+EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40;
+ QUERY PLAN
+--------------------------------
+ Index Scan using t1_pkey on t1
+ Index Cond: (a = 40)
+(2 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE b like '%789%';
+ QUERY PLAN
+--------------------------------
+ Seq Scan on t1
+ Filter: (b ~~ '%789%'::text)
+(2 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid = '(2,10)'::tid;
+ QUERY PLAN
+------------------------------------
+ Tid Scan on t1
+ TID Cond: (ctid = '(2,10)'::tid)
+(2 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+ QUERY PLAN
+------------------------------------------------------------------
+ Custom Scan (ctidscan) on t1
+ Filter: ((ctid >= '(2,115)'::tid) AND (ctid <= '(3,10)'::tid))
+(2 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 JOIN t2 ON t1.ctid = t2.ctid WHERE t1.ctid < '(2,10)'::tid AND t2.ctid > '(1,75)'::tid;
+ QUERY PLAN
+----------------------------------------------
+ Merge Join
+ Merge Cond: (t1.ctid = t2.ctid)
+ -> Sort
+ Sort Key: t1.ctid
+ -> Custom Scan (ctidscan) on t1
+ Filter: (ctid < '(2,10)'::tid)
+ -> Sort
+ Sort Key: t2.ctid
+ -> Custom Scan (ctidscan) on t2
+ Filter: (ctid > '(1,75)'::tid)
+(10 rows)
+
+SELECT ctid,* FROM t1 WHERE ctid < '(1,20)'::tid;
+ ctid | a | b
+---------+-----+----------------------------------
+ (0,1) | 1 | c4ca4238a0b923820dcc509a6f75849b
+ (0,2) | 2 | c81e728d9d4c2f636f067f89cc14862c
+ (0,3) | 3 | eccbc87e4b5ce2fe28308fd9f2a7baf3
+ (0,4) | 4 | a87ff679a2f3e71d9181a67b7542122c
+ (0,5) | 5 | e4da3b7fbbce2345d7772b0674a318d5
+ (0,6) | 6 | 1679091c5a880faf6fb5e6087eb1b2dc
+ (0,7) | 7 | 8f14e45fceea167a5a36dedd4bea2543
+ (0,8) | 8 | c9f0f895fb98ab9159f51fd0297e236d
+ (0,9) | 9 | 45c48cce2e2d7fbdea1afc51c7c6ad26
+ (0,10) | 10 | d3d9446802a44259755d38e6d163e820
+ (0,11) | 11 | 6512bd43d9caa6e02c990b0a82652dca
+ (0,12) | 12 | c20ad4d76fe97759aa27a0c99bff6710
+ (0,13) | 13 | c51ce410c124a10e0db5e4b97fc2af39
+ (0,14) | 14 | aab3238922bcc25a6f606eb525ffdc56
+ (0,15) | 15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3
+ (0,16) | 16 | c74d97b01eae257e44aa9d5bade97baf
+ (0,17) | 17 | 70efdf2ec9b086079795c442636b55fb
+ (0,18) | 18 | 6f4922f45568161a8cdf4ad2299f6d23
+ (0,19) | 19 | 1f0e3dad99908345f7439f8ffabdffc4
+ (0,20) | 20 | 98f13708210194c475687be6106a3b84
+ (0,21) | 21 | 3c59dc048e8850243be8079a5c74d079
+ (0,22) | 22 | b6d767d2f8ed5d21a44b0e5886680cb9
+ (0,23) | 23 | 37693cfc748049e45d87b8c7d8b9aacd
+ (0,24) | 24 | 1ff1de774005f8da13f42943881c655f
+ (0,25) | 25 | 8e296a067a37563370ded05f5a3bf3ec
+ (0,26) | 26 | 4e732ced3463d06de0ca9a15b6153677
+ (0,27) | 27 | 02e74f10e0327ad868d138f2b4fdd6f0
+ (0,28) | 28 | 33e75ff09dd601bbe69f351039152189
+ (0,29) | 29 | 6ea9ab1baa0efb9e19094440c317e21b
+ (0,30) | 30 | 34173cb38f07f89ddbebc2ac9128303f
+ (0,31) | 31 | c16a5320fa475530d9583c34fd356ef5
+ (0,32) | 32 | 6364d3f0f495b6ab9dcf8d3b5c6e0b01
+ (0,33) | 33 | 182be0c5cdcd5072bb1864cdee4d3d6e
+ (0,34) | 34 | e369853df766fa44e1ed0ff613f563bd
+ (0,35) | 35 | 1c383cd30b7c298ab50293adfecb7b18
+ (0,36) | 36 | 19ca14e7ea6328a42e0eb13d585e4c22
+ (0,37) | 37 | a5bfc9e07964f8dddeb95fc584cd965d
+ (0,38) | 38 | a5771bce93e200c36f7cd9dfd0e5deaa
+ (0,39) | 39 | d67d8ab4f4c10bf22aa353e27879133c
+ (0,40) | 40 | d645920e395fedad7bbbed0eca3fe2e0
+ (0,41) | 41 | 3416a75f4cea9109507cacd8e2f2aefc
+ (0,42) | 42 | a1d0c6e83f027327d8461063f4ac58a6
+ (0,43) | 43 | 17e62166fc8586dfa4d1bc0e1742c08b
+ (0,44) | 44 | f7177163c833dff4b38fc8d2872f1ec6
+ (0,45) | 45 | 6c8349cc7260ae62e3b1396831a8398f
+ (0,46) | 46 | d9d4f495e875a2e075a1a4a6e1b9770f
+ (0,47) | 47 | 67c6a1e7ce56d3d6fa748ab6d9af3fd7
+ (0,48) | 48 | 642e92efb79421734881b53e1e1b18b6
+ (0,49) | 49 | f457c545a9ded88f18ecee47145a72c0
+ (0,50) | 50 | c0c7c76d30bd3dcaefc96f40275bdc0a
+ (0,51) | 51 | 2838023a778dfaecdc212708f721b788
+ (0,52) | 52 | 9a1158154dfa42caddbd0694a4e9bdc8
+ (0,53) | 53 | d82c8d1619ad8176d665453cfb2e55f0
+ (0,54) | 54 | a684eceee76fc522773286a895bc8436
+ (0,55) | 55 | b53b3a3d6ab90ce0268229151c9bde11
+ (0,56) | 56 | 9f61408e3afb633e50cdf1b20de6f466
+ (0,57) | 57 | 72b32a1f754ba1c09b3695e0cb6cde7f
+ (0,58) | 58 | 66f041e16a60928b05a7e228a89c3799
+ (0,59) | 59 | 093f65e080a295f8076b1c5722a46aa2
+ (0,60) | 60 | 072b030ba126b2f4b2374f342be9ed44
+ (0,61) | 61 | 7f39f8317fbdb1988ef4c628eba02591
+ (0,62) | 62 | 44f683a84163b3523afe57c2e008bc8c
+ (0,63) | 63 | 03afdbd66e7929b125f8597834fa83a4
+ (0,64) | 64 | ea5d2f1c4608232e07d3aa3d998e5135
+ (0,65) | 65 | fc490ca45c00b1249bbe3554a4fdf6fb
+ (0,66) | 66 | 3295c76acbf4caaed33c36b1b5fc2cb1
+ (0,67) | 67 | 735b90b4568125ed6c3f678819b6e058
+ (0,68) | 68 | a3f390d88e4c41f2747bfa2f1b5f87db
+ (0,69) | 69 | 14bfa6bb14875e45bba028a21ed38046
+ (0,70) | 70 | 7cbbc409ec990f19c78c75bd1e06f215
+ (0,71) | 71 | e2c420d928d4bf8ce0ff2ec19b371514
+ (0,72) | 72 | 32bb90e8976aab5298d5da10fe66f21d
+ (0,73) | 73 | d2ddea18f00665ce8623e36bd4e3c7c5
+ (0,74) | 74 | ad61ab143223efbc24c7d2583be69251
+ (0,75) | 75 | d09bf41544a3365a46c9077ebb5e35c3
+ (0,76) | 76 | fbd7939d674997cdb4692d34de8633c4
+ (0,77) | 77 | 28dd2c7955ce926456240b2ff0100bde
+ (0,78) | 78 | 35f4a8d465e6e1edc05f3d8ab658c551
+ (0,79) | 79 | d1fe173d08e959397adf34b1d77e88d7
+ (0,80) | 80 | f033ab37c30201f73f142449d037028d
+ (0,81) | 81 | 43ec517d68b6edd3015b3edc9a11367b
+ (0,82) | 82 | 9778d5d219c5080b9a6a17bef029331c
+ (0,83) | 83 | fe9fc289c3ff0af142b6d3bead98a923
+ (0,84) | 84 | 68d30a9594728bc39aa24be94b319d21
+ (0,85) | 85 | 3ef815416f775098fe977004015c6193
+ (0,86) | 86 | 93db85ed909c13838ff95ccfa94cebd9
+ (0,87) | 87 | c7e1249ffc03eb9ded908c236bd1996d
+ (0,88) | 88 | 2a38a4a9316c49e5a833517c45d31070
+ (0,89) | 89 | 7647966b7343c29048673252e490f736
+ (0,90) | 90 | 8613985ec49eb8f757ae6439e879bb2a
+ (0,91) | 91 | 54229abfcfa5649e7003b83dd4755294
+ (0,92) | 92 | 92cc227532d17e56e07902b254dfad10
+ (0,93) | 93 | 98dce83da57b0395e163467c9dae521b
+ (0,94) | 94 | f4b9ec30ad9f68f89b29639786cb62ef
+ (0,95) | 95 | 812b4ba287f5ee0bc9d43bbf5bbe87fb
+ (0,96) | 96 | 26657d5ff9020d2abefe558796b99584
+ (0,97) | 97 | e2ef524fbf3d9fe611d5a8e90fefdc9c
+ (0,98) | 98 | ed3d2c21991e3bef5e069713af9fa6ca
+ (0,99) | 99 | ac627ab1ccbdb62ec96e702f07f6425b
+ (0,100) | 100 | f899139df5e1059396431415e770c6dd
+ (0,101) | 101 | 38b3eff8baf56627478ec76a704e9b52
+ (0,102) | 102 | ec8956637a99787bd197eacd77acce5e
+ (0,103) | 103 | 6974ce5ac660610b44d9b9fed0ff9548
+ (0,104) | 104 | c9e1074f5b3f9fc8ea15d152add07294
+ (0,105) | 105 | 65b9eea6e1cc6bb9f0cd2a47751a186f
+ (0,106) | 106 | f0935e4cd5920aa6c7c996a5ee53a70f
+ (0,107) | 107 | a97da629b098b75c294dffdc3e463904
+ (0,108) | 108 | a3c65c2974270fd093ee8a9bf8ae7d0b
+ (0,109) | 109 | 2723d092b63885e0d7c260cc007e8b9d
+ (0,110) | 110 | 5f93f983524def3dca464469d2cf9f3e
+ (0,111) | 111 | 698d51a19d8a121ce581499d7b701668
+ (0,112) | 112 | 7f6ffaa6bb0b408017b62254211691b5
+ (0,113) | 113 | 73278a4a86960eeb576a8fd4c9ec6997
+ (0,114) | 114 | 5fd0b37cd7dbbb00f97ba6ce92bf5add
+ (0,115) | 115 | 2b44928ae11fb9384c4cf38708677c48
+ (0,116) | 116 | c45147dee729311ef5b5c3003946c48f
+ (0,117) | 117 | eb160de1de89d9058fcb0b968dbbbd68
+ (0,118) | 118 | 5ef059938ba799aaa845e1c2e8a762bd
+ (0,119) | 119 | 07e1cd7dca89a1678042477183b7ac3f
+ (0,120) | 120 | da4fb5c6e93e74d3df8527599fa62642
+ (1,1) | 121 | 4c56ff4ce4aaf9573aa5dff913df997a
+ (1,2) | 122 | a0a080f42e6f13b3a2df133f073095dd
+ (1,3) | 123 | 202cb962ac59075b964b07152d234b70
+ (1,4) | 124 | c8ffe9a587b126f152ed3d89a146b445
+ (1,5) | 125 | 3def184ad8f4755ff269862ea77393dd
+ (1,6) | 126 | 069059b7ef840f0c74a814ec9237b6ec
+ (1,7) | 127 | ec5decca5ed3d6b8079e2e7e7bacc9f2
+ (1,8) | 128 | 76dc611d6ebaafc66cc0879c71b5db5c
+ (1,9) | 129 | d1f491a404d6854880943e5c3cd9ca25
+ (1,10) | 130 | 9b8619251a19057cff70779273e95aa6
+ (1,11) | 131 | 1afa34a7f984eeabdbb0a7d494132ee5
+ (1,12) | 132 | 65ded5353c5ee48d0b7d48c591b8f430
+ (1,13) | 133 | 9fc3d7152ba9336a670e36d0ed79bc43
+ (1,14) | 134 | 02522a2b2726fb0a03bb19f2d8d9524d
+ (1,15) | 135 | 7f1de29e6da19d22b51c68001e7e0e54
+ (1,16) | 136 | 42a0e188f5033bc65bf8d78622277c4e
+ (1,17) | 137 | 3988c7f88ebcb58c6ce932b957b6f332
+ (1,18) | 138 | 013d407166ec4fa56eb1e1f8cbe183b9
+ (1,19) | 139 | e00da03b685a0dd18fb6a08af0923de0
+(139 rows)
+
+SELECT ctid,* FROM t1 WHERE ctid > '(4,0)'::tid;
+ ctid | a | b
+------+---+---
+(0 rows)
+
+SELECT ctid,* FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+ ctid | a | b
+---------+-----+----------------------------------
+ (2,115) | 355 | 82cec96096d4281b7c95cd7e74623496
+ (2,116) | 356 | 6c524f9d5d7027454a783c841250ba71
+ (2,117) | 357 | fb7b9ffa5462084c5f4e7e85a093e6d7
+ (2,118) | 358 | aa942ab2bfa6ebda4840e7360ce6e7ef
+ (2,119) | 359 | c058f544c737782deacefa532d9add4c
+ (2,120) | 360 | e7b24b112a44fdd9ee93bdf998c6ca0e
+ (3,1) | 361 | 52720e003547c70561bf5e03b95aa99f
+ (3,2) | 362 | c3e878e27f52e2a57ace4d9a76fd9acf
+ (3,3) | 363 | 00411460f7c92d2124a67ea0f4cb5f85
+ (3,4) | 364 | bac9162b47c56fc8a4d2a519803d51b3
+ (3,5) | 365 | 9be40cee5b0eee1462c82c6964087ff9
+ (3,6) | 366 | 5ef698cd9fe650923ea331c15af3b160
+ (3,7) | 367 | 05049e90fa4f5039a8cadc6acbb4b2cc
+ (3,8) | 368 | cf004fdc76fa1a4f25f62e0eb5261ca3
+ (3,9) | 369 | 0c74b7f78409a4022a2c4c5a5ca3ee19
+ (3,10) | 370 | d709f38ef758b5066ef31b18039b8ce5
+(16 rows)
+
+SELECT t1.ctid,* FROM t1 JOIN t2 ON t1.ctid = t2.ctid WHERE t1.ctid < '(2,10)'::tid AND t2.ctid > '(1,75)'::tid;
+ ctid | a | b | x | y
+--------+-----+----------------------------------+-----+------------------------------------------------------------------
+ (1,76) | 196 | 084b6fbb10729ed4da8c3d3f5a3ae7c9 | 157 | 6c4b761a28b734fe93831e3fb400ce876c4b761a28b734fe93831e3fb400ce87
+ (1,77) | 197 | 85d8ce590ad8981ca2c8286f79f59954 | 158 | 06409663226af2f3114485aa4e0a23b406409663226af2f3114485aa4e0a23b4
+ (1,78) | 198 | 0e65972dce68dad4d52d063967f0a705 | 159 | 140f6969d5213fd0ece03148e62e461e140f6969d5213fd0ece03148e62e461e
+ (1,79) | 199 | 84d9ee44e457ddef7f2c4f25dc8fa865 | 160 | b73ce398c39f506af761d2277d853a92b73ce398c39f506af761d2277d853a92
+ (1,80) | 200 | 3644a684f98ea8fe223c713b77189a77 | 161 | bd4c9ab730f5513206b999ec0d90d1fbbd4c9ab730f5513206b999ec0d90d1fb
+ (1,81) | 201 | 757b505cfd34c64c85ca5b5690ee5293 | 162 | 82aa4b0af34c2313a562076992e50aa382aa4b0af34c2313a562076992e50aa3
+ (2,1) | 241 | f340f1b1f65b6df5b5e3f94d95b11daf | 163 | 0777d5c17d4066b82ab86dff8a46af6f0777d5c17d4066b82ab86dff8a46af6f
+ (2,2) | 242 | e4a6222cdb5b34375400904f03d8e6a5 | 164 | fa7cdfad1a5aaf8370ebeda47a1ff1c3fa7cdfad1a5aaf8370ebeda47a1ff1c3
+ (2,3) | 243 | cb70ab375662576bd1ac5aaf16b3fca4 | 165 | 9766527f2b5d3e95d4a733fcfb77bd7e9766527f2b5d3e95d4a733fcfb77bd7e
+ (2,4) | 244 | 9188905e74c28e489b44e954ec0b9bca | 166 | 7e7757b1e12abcb736ab9a754ffb617a7e7757b1e12abcb736ab9a754ffb617a
+ (2,5) | 245 | 0266e33d3f546cb5436a10798e657d97 | 167 | 5878a7ab84fb43402106c575658472fa5878a7ab84fb43402106c575658472fa
+ (2,6) | 246 | 38db3aed920cf82ab059bfccbd02be6a | 168 | 006f52e9102a8d3be2fe5614f42ba989006f52e9102a8d3be2fe5614f42ba989
+ (2,7) | 247 | 3cec07e9ba5f5bb252d13f5f431e4bbb | 169 | 3636638817772e42b59d74cff571fbb33636638817772e42b59d74cff571fbb3
+ (2,8) | 248 | 621bf66ddb7c962aa0d22ac97d69b793 | 170 | 149e9677a5989fd342ae44213df68868149e9677a5989fd342ae44213df68868
+ (2,9) | 249 | 077e29b11be80ab57e1a2ecabb7da330 | 171 | a4a042cf4fd6bfb47701cbc8a1653adaa4a042cf4fd6bfb47701cbc8a1653ada
+(15 rows)
+
+-- Test creanup
+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/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 1c1491c..fe81929 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -78,7 +78,7 @@ ignore: random
# ----------
# Another group of parallel tests
# ----------
-test: select_into select_distinct select_distinct_on select_implicit select_having subselect union case join aggregates transactions random portals arrays btree_index hash_index update namespace prepared_xacts delete
+test: select_into select_distinct select_distinct_on select_implicit select_having subselect union case join aggregates transactions random portals arrays btree_index hash_index update namespace prepared_xacts delete custom_scan
# ----------
# Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index c4d451a..a2287d8 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -91,6 +91,7 @@ test: btree_index
test: hash_index
test: update
test: delete
+test: custom_scan
test: namespace
test: prepared_xacts
test: privileges