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