contrib/ctidscan/Makefile | 19 + contrib/ctidscan/ctidscan--1.0.sql | 12 + contrib/ctidscan/ctidscan--unpackaged-1.0.sql | 0 contrib/ctidscan/ctidscan.c | 951 ++++++++++++++++++++++++++ contrib/ctidscan/ctidscan.control | 5 + contrib/ctidscan/expected/ctidscan.out | 294 ++++++++ contrib/ctidscan/sql/ctidscan.sql | 50 ++ doc/src/sgml/catalogs.sgml | 59 ++ doc/src/sgml/ref/allfiles.sgml | 2 + doc/src/sgml/ref/create_custom_plan.sgml | 135 ++++ doc/src/sgml/ref/drop_custom_plan.sgml | 108 +++ src/backend/catalog/Makefile | 2 +- src/backend/catalog/dependency.c | 11 +- src/backend/catalog/objectaddress.c | 59 ++ src/backend/commands/Makefile | 2 +- src/backend/commands/custom_plan.c | 186 +++++ src/backend/commands/dropcmds.c | 5 + src/backend/commands/event_trigger.c | 2 + src/backend/commands/explain.c | 37 + src/backend/executor/Makefile | 2 +- src/backend/executor/execAmi.c | 23 + src/backend/executor/execProcnode.c | 19 + src/backend/executor/nodeCustom.c | 135 ++++ src/backend/nodes/copyfuncs.c | 34 + src/backend/nodes/equalfuncs.c | 14 + src/backend/nodes/outfuncs.c | 33 + src/backend/optimizer/path/allpaths.c | 30 +- src/backend/optimizer/plan/createplan.c | 97 ++- src/backend/optimizer/plan/setrefs.c | 11 +- src/backend/optimizer/plan/subselect.c | 15 + src/backend/optimizer/util/pathnode.c | 110 +++ src/backend/parser/gram.y | 93 ++- src/backend/tcop/utility.c | 16 + src/backend/utils/adt/ruleutils.c | 54 ++ src/backend/utils/cache/syscache.c | 23 + src/include/catalog/dependency.h | 1 + src/include/catalog/indexing.h | 6 + src/include/catalog/pg_custom_plan.h | 49 ++ src/include/catalog/pg_operator.h | 3 + src/include/commands/defrem.h | 5 + src/include/executor/nodeCustom.h | 30 + src/include/nodes/execnodes.h | 41 ++ src/include/nodes/nodes.h | 5 + src/include/nodes/parsenodes.h | 13 + src/include/nodes/plannodes.h | 40 ++ src/include/nodes/relation.h | 28 + src/include/optimizer/pathnode.h | 14 + src/include/optimizer/planmain.h | 2 + src/include/parser/kwlist.h | 4 + src/include/utils/syscache.h | 2 + src/test/regress/expected/sanity_check.out | 1 + 51 files changed, 2856 insertions(+), 36 deletions(-) diff --git a/contrib/ctidscan/Makefile b/contrib/ctidscan/Makefile new file mode 100644 index 0000000..1e476a6 --- /dev/null +++ b/contrib/ctidscan/Makefile @@ -0,0 +1,19 @@ +# contrib/ctidscan/Makefile + +MODULES = ctidscan + +EXTENSION = ctidscan +DATA = ctidscan--1.0.sql + +REGRESS = ctidscan + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/ctidscan +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/ctidscan/ctidscan--1.0.sql b/contrib/ctidscan/ctidscan--1.0.sql new file mode 100644 index 0000000..3eec965 --- /dev/null +++ b/contrib/ctidscan/ctidscan--1.0.sql @@ -0,0 +1,12 @@ +-- +-- Create ctidscan handler function +-- +CREATE FUNCTION ctidscanaddpath(internal) + RETURNS pg_catalog.void + AS 'MODULE_PATHNAME','CtidScanAddPath' + LANGUAGE C STRICT; + +-- +-- Create a custom-plan provider +-- +CREATE CUSTOM PLAN ctidscan FOR scan PROVIDER ctidscanaddpath; diff --git a/contrib/ctidscan/ctidscan--unpackaged-1.0.sql b/contrib/ctidscan/ctidscan--unpackaged-1.0.sql new file mode 100644 index 0000000..e69de29 diff --git a/contrib/ctidscan/ctidscan.c b/contrib/ctidscan/ctidscan.c new file mode 100644 index 0000000..460b38e --- /dev/null +++ b/contrib/ctidscan/ctidscan.c @@ -0,0 +1,951 @@ +/* + * ctidscan.c + * + * Definition of Custom TidScan implementation. + * + * It is designed to demonstrate Custom Scan APIs; that allows to override + * a part of executor node. This extension focus on a workload that tries + * to fetch records with tid larger or less than a particular value. + * In case when inequality operators were given, this module construct + * a custom scan path that enables to skip records not to be read. Then, + * if it was the cheapest one, it shall be used to run the query. + * Custom Scan APIs callbacks this extension when executor tries to fetch + * underlying records, then it utilizes existing heap_getnext() but seek + * the records to be read prior to fetching the first record. + * + * Portions Copyright (c) 2014, PostgreSQL Global Development Group + */ +#include "postgres.h" +#include "access/relscan.h" +#include "access/sysattr.h" +#include "catalog/pg_custom_plan.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_type.h" +#include "commands/defrem.h" +#include "commands/explain.h" +#include "executor/executor.h" +#include "executor/nodeCustom.h" +#include "fmgr.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#include "optimizer/clauses.h" +#include "optimizer/cost.h" +#include "optimizer/paths.h" +#include "optimizer/pathnode.h" +#include "optimizer/plancat.h" +#include "optimizer/planmain.h" +#include "optimizer/placeholder.h" +#include "optimizer/restrictinfo.h" +#include "optimizer/subselect.h" +#include "parser/parsetree.h" +#include "storage/bufmgr.h" +#include "storage/itemptr.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/rel.h" +#include "utils/spccache.h" + +PG_MODULE_MAGIC; + +typedef struct { + CustomPath cpath; + List *ctid_quals; +} CtidScanPath; + +typedef struct { + CustomPlan cplan; + List *ctid_quals; +} CtidScanPlan; + +typedef struct { + CustomPlanState cps; + List *ctid_quals; /* list of ExprState for inequality ops */ +} CtidScanState; + +static CustomPathMethods ctidscan_path_methods; +static CustomPlanMethods ctidscan_plan_methods; +static CustomExecMethods ctidscan_exec_methods; + +/* function declarations */ +void _PG_init(void); +Datum CtidScanAddPath(PG_FUNCTION_ARGS); + +#define IsCTIDVar(node,rtindex) \ + ((node) != NULL && \ + IsA((node), Var) && \ + ((Var *) (node))->varno == (rtindex) && \ + ((Var *) (node))->varattno == SelfItemPointerAttributeNumber && \ + ((Var *) (node))->varlevelsup == 0) + +/* + * CTidQualFromExpr + * + * It checks whether the given restriction clauses enables to determine + * the zone to be scanned, or not. If one or more restriction clauses are + * available, it returns a list of them, or NIL elsewhere. + * The caller can consider all the conditions are chained with AND- + * boolean operator, so all the operator works for narrowing down the + * scope of custom tid scan. + */ +static List * +CTidQualFromExpr(Node *expr, int varno) +{ + if (is_opclause(expr)) + { + OpExpr *op = (OpExpr *) expr; + Node *arg1; + Node *arg2; + Node *other = NULL; + + /* only inequality operators are candidate */ + if (op->opno != TIDLessOperator && + op->opno != TIDLessEqualOperator && + op->opno != TIDGreaterOperator && + op->opno != TIDGreaterEqualOperator) + return NULL; + + if (list_length(op->args) != 2) + return false; + + arg1 = linitial(op->args); + arg2 = lsecond(op->args); + + if (IsCTIDVar(arg1, varno)) + other = arg2; + else if (IsCTIDVar(arg2, varno)) + other = arg1; + else + return NULL; + if (exprType(other) != TIDOID) + return NULL; /* probably can't happen */ + /* The other argument must be a pseudoconstant */ + if (!is_pseudo_constant_clause(other)) + return NULL; + + return list_make1(copyObject(op)); + } + else if (and_clause(expr)) + { + List *rlst = NIL; + ListCell *lc; + + foreach(lc, ((BoolExpr *) expr)->args) + { + List *temp = CTidQualFromExpr((Node *) lfirst(lc), varno); + + rlst = list_concat(rlst, temp); + } + return rlst; + } + return NIL; +} + +/* + * CTidEstimateCosts + * + * It estimates cost to scan the target relation according to the given + * restriction clauses. Its logic to scan relations are almost same as + * SeqScan doing, because it uses regular heap_getnext(), except for + * the number of tuples to be scanned if restriction clauses work well. +*/ +static void +CTidEstimateCosts(PlannerInfo *root, + RelOptInfo *baserel, + CtidScanPath *ctid_path) +{ + Path *path = &ctid_path->cpath.path; + List *ctid_quals = ctid_path->ctid_quals; + ListCell *lc; + double ntuples; + ItemPointerData ip_min; + ItemPointerData ip_max; + bool has_min_val = false; + bool has_max_val = false; + BlockNumber num_pages; + Cost startup_cost = 0; + Cost run_cost = 0; + Cost cpu_per_tuple; + QualCost qpqual_cost; + QualCost ctid_qual_cost; + double spc_random_page_cost; + + /* Should only be applied to base relations */ + Assert(baserel->relid > 0); + Assert(baserel->rtekind == RTE_RELATION); + + /* Mark the path with the correct row estimate */ + if (path->param_info) + path->rows = path->param_info->ppi_rows; + else + path->rows = baserel->rows; + + /* Estimate how many tuples we may retrieve */ + ItemPointerSet(&ip_min, 0, 0); + ItemPointerSet(&ip_max, MaxBlockNumber, MaxOffsetNumber); + foreach (lc, ctid_quals) + { + OpExpr *op = lfirst(lc); + Oid opno; + Node *other; + + Assert(is_opclause(op)); + if (IsCTIDVar(linitial(op->args), baserel->relid)) + { + opno = op->opno; + other = lsecond(op->args); + } + else if (IsCTIDVar(lsecond(op->args), baserel->relid)) + { + /* To simplifies, we assume as if Var node is 1st argument */ + opno = get_commutator(op->opno); + other = linitial(op->args); + } + else + elog(ERROR, "could not identify CTID variable"); + + if (IsA(other, Const)) + { + ItemPointer ip = (ItemPointer)(((Const *) other)->constvalue); + + /* + * Just an rough estimation, we don't distinct inequality and + * inequality-or-equal operator. + */ + switch (opno) + { + case TIDLessOperator: + case TIDLessEqualOperator: + if (ItemPointerCompare(ip, &ip_max) < 0) + ItemPointerCopy(ip, &ip_max); + has_max_val = true; + break; + case TIDGreaterOperator: + case TIDGreaterEqualOperator: + if (ItemPointerCompare(ip, &ip_min) > 0) + ItemPointerCopy(ip, &ip_min); + has_min_val = true; + break; + default: + elog(ERROR, "unexpected operator code: %u", op->opno); + break; + } + } + } + + /* estimated number of tuples in this relation */ + ntuples = baserel->pages * baserel->tuples; + + if (has_min_val && has_max_val) + { + /* case of both side being bounded */ + BlockNumber bnum_max = BlockIdGetBlockNumber(&ip_max.ip_blkid); + BlockNumber bnum_min = BlockIdGetBlockNumber(&ip_min.ip_blkid); + + bnum_max = Min(bnum_max, baserel->pages); + bnum_min = Max(bnum_min, 0); + num_pages = Min(bnum_max - bnum_min + 1, 1); + } + else if (has_min_val) + { + /* case of only lower side being bounded */ + BlockNumber bnum_max = baserel->pages; + BlockNumber bnum_min = BlockIdGetBlockNumber(&ip_min.ip_blkid); + + bnum_min = Max(bnum_min, 0); + num_pages = Min(bnum_max - bnum_min + 1, 1); + } + else if (has_max_val) + { + /* case of only upper side being bounded */ + BlockNumber bnum_max = BlockIdGetBlockNumber(&ip_max.ip_blkid); + BlockNumber bnum_min = 0; + + bnum_max = Min(bnum_max, baserel->pages); + num_pages = Min(bnum_max - bnum_min + 1, 1); + } + else + { + /* + * Just a rough estimation. We assume half of records shall be + * read using this restriction clause, but undeterministic untill + * executor run it actually. + */ + num_pages = Max((baserel->pages + 1) / 2, 1); + } + ntuples *= ((double) num_pages) / ((double) baserel->pages); + + /* + * The TID qual expressions will be computed once, any other baserestrict + * quals once per retrieved tuple. + */ + cost_qual_eval(&ctid_qual_cost, ctid_quals, root); + + /* fetch estimated page cost for tablespace containing table */ + get_tablespace_page_costs(baserel->reltablespace, + &spc_random_page_cost, + NULL); + + /* disk costs --- assume each tuple on a different page */ + run_cost += spc_random_page_cost * ntuples; + + /* + * Add scanning CPU costs + * (logic copied from get_restriction_qual_cost) + */ + if (path->param_info) + { + /* Include costs of pushed-down clauses */ + cost_qual_eval(&qpqual_cost, path->param_info->ppi_clauses, root); + + qpqual_cost.startup += baserel->baserestrictcost.startup; + qpqual_cost.per_tuple += baserel->baserestrictcost.per_tuple; + } + else + qpqual_cost = baserel->baserestrictcost; + + /* + * We don't decrease cost for the inequality operators, because + * it is subset of qpquals and still in. + */ + startup_cost += qpqual_cost.startup + ctid_qual_cost.per_tuple; + cpu_per_tuple = cpu_tuple_cost + qpqual_cost.per_tuple - + ctid_qual_cost.per_tuple; + run_cost = cpu_per_tuple * ntuples; + + path->startup_cost = startup_cost; + path->total_cost = startup_cost + run_cost; +} + +/* + * CreateCtidScanPlan - A method of CustomPath; that populate a custom + * object being delivered from CustomPlan type, according to the supplied + * CustomPath object. + */ +static CustomPlan * +CreateCtidScanPlan(PlannerInfo *root, CustomPath *best_path) +{ + CtidScanPath *ctid_path = (CtidScanPath *) best_path; + CtidScanPlan *ctid_plan; + + ctid_plan = palloc0(sizeof(CtidScanPlan)); + NodeSetTag(ctid_plan, T_CustomPlan); + ctid_plan->cplan.methods = &ctidscan_plan_methods; + ctid_plan->ctid_quals = ctid_path->ctid_quals; + + return &ctid_plan->cplan; +} + +/* + * InitCtidScanPlan + * + * main portion to initialize CustomPlan node. + */ +static Plan * +InitCtidScanPlan(CustomPlan *custom_plan, + PlannerInfo *root, CustomPath *best_path, + List *tlist, List *clauses) +{ + CtidScanPlan *ctid_scan = (CtidScanPlan *)custom_plan; + + Assert(ctid_scan->cplan.scan.scanrelid > 0); + + /* Set targetlist as is */ + ctid_scan->cplan.scan.plan.targetlist = tlist; + /* Reduce RestrictInfo list to bare expressions */ + ctid_scan->cplan.scan.plan.qual + = extract_actual_clauses(clauses, false); + + /* + * If there are any pseudoconstant clauses attached to this node, insert a + * gating Result node that evaluates the pseudoconstants as one-time + * quals. + */ + if (root->hasPseudoConstantQuals) + { + List *pseudoconstants = extract_actual_clauses(clauses, true); + + if (pseudoconstants != NIL) + return (Plan *) make_result(root, + tlist, + (Node *) pseudoconstants, + (Plan *) ctid_scan); + } + return &ctid_scan->cplan.scan.plan; +} + +/* + * TextOutCtidScanPath - A method of CustomPath; that shows a text + * representation of the supplied CustomPath object. + */ +static void +TextOutCtidScanPath(StringInfo str, const CustomPath *cpath) +{ + CtidScanPath *ctid_path = (CtidScanPath *)cpath; + + appendStringInfo(str, " :ctid_quals %s", + nodeToString(ctid_path->ctid_quals)); +} + +/* + * SetCtidScanPlanRef - A method of CustomPlan; that fixes up rtindex + * of Var nodes + */ +#define fix_scan_list(root, lst, rtoffset) \ + ((List *) fix_scan_expr(root, (Node *) (lst), rtoffset)) + +static void +SetCtidScanPlanRef(PlannerInfo *root, + CustomPlan *custom_plan, + int rtoffset) +{ + CtidScanPlan *ctidscan = (CtidScanPlan *) custom_plan; + Scan *scan = &ctidscan->cplan.scan; + + scan->scanrelid += rtoffset; + scan->plan.targetlist = + fix_scan_list(root, scan->plan.targetlist, rtoffset); + scan->plan.qual = + fix_scan_list(root, scan->plan.qual, rtoffset); + ctidscan->ctid_quals = + fix_scan_list(root, ctidscan->ctid_quals, rtoffset); +} + +/* + * SupportCtidBackwardScan - A method of CustomPlan; that informs the core + * backend whether this custom-plan node support backward scan or not. + */ +static bool +SupportCtidBackwardScan(CustomPlan *custom_plan) +{ + return true; +} + +/* + * FinalizeCtidScanPlan - A method of CustomPlan; that handles callbacks + * by finalize_plan(). + */ +static Bitmapset * +FinalizeCtidScanPlan(PlannerInfo *root, + CustomPlan *custom_plan, + Bitmapset *paramids, + Bitmapset *valid_params, + Bitmapset *scan_params, + bool (*finalize_primnode)(), + void *finalize_context) +{ + CtidScanPlan *ctid_plan = (CtidScanPlan *) custom_plan; + + /* applies finalize_primnode() on ctid_quals also */ + finalize_primnode((Node *)ctid_plan->ctid_quals, finalize_context); + + return bms_add_members(paramids, scan_params); +} + +/* + * CreateCtidScanState - A method of CustomPlan; that populate a custom + * object being delivered from CustomPlanState type, according to the + * supplied CustomPath object. + */ +static CustomPlanState * +CreateCtidScanState(CustomPlan *custom_plan) +{ + CtidScanState *ctss = palloc0(sizeof(CtidScanState)); + + NodeSetTag(ctss, T_CustomPlanState); + ctss->cps.methods = &ctidscan_exec_methods; + + return &ctss->cps; +} + +/* + * TextOutCtidScanPlan - A method of CustomPlan; that generates text + * representation of the given object. + */ +static void +TextOutCtidScanPlan(StringInfo str, const CustomPlan *node) +{ + CtidScanPlan *ctid_plan = (CtidScanPlan *) node; + + appendStringInfo(str, " :ctid_quals %s", + nodeToString(ctid_plan->ctid_quals)); +} + +/* + * CopyCtidScanPlan - A method of CustomPlan; that create a copied object. + */ +static CustomPlan * +CopyCtidScanPlan(const CustomPlan *from) +{ + CtidScanPlan *oldnode = (CtidScanPlan *) from; + CtidScanPlan *newnode = palloc0(sizeof(CtidScanPlan)); + + NodeSetTag(newnode, T_CustomPlan); + newnode->ctid_quals = copyObject(oldnode->ctid_quals); + + return &newnode->cplan; +} + +/* + * BeginCtidScan - A method of CustomPlanState; that initializes + * the supplied CtidScanState object, at begining of the executor. + */ +static void +BeginCtidScan(CustomPlanState *node, EState *estate, int eflags) +{ + CtidScanState *ctss = (CtidScanState *) node; + CtidScanPlan *ctid_plan = (CtidScanPlan *) node->ss.ps.plan; + + /* + * In case of custom-plan provider that offers an alternative way + * to scan a particular relation, most of the needed initialization, + * like relation open or assignment of scan tuple-slot or projection + * info, shall be done by the core implementation. So, all we need + * to have is initialization of own local properties. + */ + ctss->ctid_quals = (List *) + ExecInitExpr((Expr *)ctid_plan->ctid_quals, &node->ss.ps); + + /* Do nothing anymore in EXPLAIN (no ANALYZE) case. */ + if (eflags & EXEC_FLAG_EXPLAIN_ONLY) + return; + + /* scandesc shall be set later */ + ctss->cps.ss.ss_currentScanDesc = NULL; +} + +/* + * ReScanCtidScan - A method of CustomPlanState; that rewind the current + * seek position. + */ +static void +ReScanCtidScan(CustomPlanState *node) +{ + CtidScanState *ctss = (CtidScanState *)node; + HeapScanDesc scan = ctss->cps.ss.ss_currentScanDesc; + EState *estate = node->ss.ps.state; + ScanDirection direction = estate->es_direction; + Relation relation = ctss->cps.ss.ss_currentRelation; + ExprContext *econtext = ctss->cps.ss.ps.ps_ExprContext; + ScanKeyData keys[2]; + bool has_ubound = false; + bool has_lbound = false; + ItemPointerData ip_max; + ItemPointerData ip_min; + ListCell *lc; + + /* once close the existing scandesc, if any */ + if (scan) + { + heap_endscan(scan); + scan = ctss->cps.ss.ss_currentScanDesc = NULL; + } + + /* walks on the inequality operators */ + foreach (lc, ctss->ctid_quals) + { + FuncExprState *fexstate = (FuncExprState *) lfirst(lc); + OpExpr *op = (OpExpr *)fexstate->xprstate.expr; + Node *arg1 = linitial(op->args); + Node *arg2 = lsecond(op->args); + Index scanrelid; + Oid opno; + ExprState *exstate; + ItemPointer itemptr; + bool isnull; + + scanrelid = ((Scan *)ctss->cps.ss.ps.plan)->scanrelid; + if (IsCTIDVar(arg1, scanrelid)) + { + exstate = (ExprState *) lsecond(fexstate->args); + opno = op->opno; + } + else if (IsCTIDVar(arg2, scanrelid)) + { + exstate = (ExprState *) linitial(fexstate->args); + opno = get_commutator(op->opno); + } + else + elog(ERROR, "could not identify CTID variable"); + + itemptr = (ItemPointer) + DatumGetPointer(ExecEvalExprSwitchContext(exstate, + econtext, + &isnull, + NULL)); + if (isnull) + { + /* + * Whole of the restriction clauses chained with AND- boolean + * operators because false, if one of the clauses has NULL result. + * So, we can immediately break the evaluation to inform caller + * it does not make sense to scan any more. + * In this case, scandesc is kept to NULL. + */ + return; + } + + switch (opno) + { + case TIDLessOperator: + if (!has_ubound || + ItemPointerCompare(itemptr, &ip_max) <= 0) + { + ScanKeyInit(&keys[0], + SelfItemPointerAttributeNumber, + BTLessStrategyNumber, + F_TIDLT, + PointerGetDatum(itemptr)); + ItemPointerCopy(itemptr, &ip_max); + has_ubound = true; + } + break; + + case TIDLessEqualOperator: + if (!has_ubound || + ItemPointerCompare(itemptr, &ip_max) < 0) + { + ScanKeyInit(&keys[0], + SelfItemPointerAttributeNumber, + BTLessEqualStrategyNumber, + F_TIDLE, + PointerGetDatum(itemptr)); + ItemPointerCopy(itemptr, &ip_max); + has_ubound = true; + } + break; + + case TIDGreaterOperator: + if (!has_lbound || + ItemPointerCompare(itemptr, &ip_min) >= 0) + { + ScanKeyInit(&keys[1], + SelfItemPointerAttributeNumber, + BTGreaterStrategyNumber, + F_TIDGT, + PointerGetDatum(itemptr)); + ItemPointerCopy(itemptr, &ip_min); + has_lbound = true; + } + break; + + case TIDGreaterEqualOperator: + if (!has_lbound || + ItemPointerCompare(itemptr, &ip_min) > 0) + { + ScanKeyInit(&keys[1], + SelfItemPointerAttributeNumber, + BTGreaterEqualStrategyNumber, + F_TIDGE, + PointerGetDatum(itemptr)); + ItemPointerCopy(itemptr, &ip_min); + has_lbound = true; + } + break; + + default: + elog(ERROR, "unsupported operator"); + break; + } + } + + /* begin heapscan with the key above */ + if (has_ubound && has_lbound) + scan = heap_beginscan(relation, estate->es_snapshot, 2, &keys[0]); + else if (has_ubound) + scan = heap_beginscan(relation, estate->es_snapshot, 1, &keys[0]); + else if (has_lbound) + scan = heap_beginscan(relation, estate->es_snapshot, 1, &keys[1]); + else + scan = heap_beginscan(relation, estate->es_snapshot, 0, NULL); + + /* Seek the starting position, if possible */ + if (direction == ForwardScanDirection && has_lbound) + { + BlockNumber blknum = Min(BlockIdGetBlockNumber(&ip_min.ip_blkid), + scan->rs_nblocks - 1); + scan->rs_startblock = blknum; + } + else if (direction == BackwardScanDirection && has_ubound) + { + BlockNumber blknum = Min(BlockIdGetBlockNumber(&ip_max.ip_blkid), + scan->rs_nblocks - 1); + scan->rs_startblock = blknum; + } + ctss->cps.ss.ss_currentScanDesc = scan; +} + +/* + * CTidAccessCustomScan + * + * Access method of ExecCtidScan below. It fetches a tuple from the underlying + * heap scan that was started from the point according to the tid clauses. + */ +static TupleTableSlot * +CTidAccessCustomScan(CustomPlanState *node) +{ + CtidScanState *ctss = (CtidScanState *) node; + HeapScanDesc scan; + TupleTableSlot *slot; + EState *estate = node->ss.ps.state; + ScanDirection direction = estate->es_direction; + HeapTuple tuple; + + if (!ctss->cps.ss.ss_currentScanDesc) + ReScanCtidScan(node); + scan = ctss->cps.ss.ss_currentScanDesc; + Assert(scan != NULL); + + /* + * get the next tuple from the table + */ + tuple = heap_getnext(scan, direction); + if (!HeapTupleIsValid(tuple)) + return NULL; + + slot = ctss->cps.ss.ss_ScanTupleSlot; + ExecStoreTuple(tuple, slot, scan->rs_cbuf, false); + + return slot; +} + +static bool +CTidRecheckCustomScan(CustomPlanState *node, TupleTableSlot *slot) +{ + return true; +} + +/* + * ExecCtidScan - A method of CustomPlanState; that fetches a tuple + * from the relation, if exist anymore. + */ +static TupleTableSlot * +ExecCtidScan(CustomPlanState *node) +{ + return ExecScan(&node->ss, + (ExecScanAccessMtd) CTidAccessCustomScan, + (ExecScanRecheckMtd) CTidRecheckCustomScan); +} + +/* + * CTidEndCustomScan - A method of CustomPlanState; that closes heap and + * scan descriptor, and release other related resources. + */ +static void +EndCtidScan(CustomPlanState *node) +{ + CtidScanState *ctss = (CtidScanState *)node; + + if (ctss->cps.ss.ss_currentScanDesc) + heap_endscan(ctss->cps.ss.ss_currentScanDesc); +} + +/* + * ExplanCtidScanTargetRel - A method of CustomPlanState; that output + * relation's name to be scanned. + */ +static void +ExplanCtidScanTargetRel(CustomPlanState *node, ExplainState *es) +{ + CtidScanState *ctss = (CtidScanState *) node; + CtidScanPlan *ctid_plan = (CtidScanPlan *) ctss->cps.ss.ps.plan; + Index rti = ctid_plan->cplan.scan.scanrelid; + RangeTblEntry *rte; + char *objectname = NULL; + char *namespace = NULL; + char *refname; + + /* logic copied from ExplainTargetRel */ + rte = rt_fetch(rti, es->rtable); + refname = (char *) list_nth(es->rtable_names, rti - 1); + if (refname == NULL) + refname = rte->eref->aliasname; + + Assert(rte->rtekind == RTE_RELATION); + objectname = get_rel_name(rte->relid); + if (es->verbose) + namespace = get_namespace_name(get_rel_namespace(rte->relid)); + + if (es->format == EXPLAIN_FORMAT_TEXT) + { + appendStringInfoString(es->str, " on"); + if (namespace != NULL) + appendStringInfo(es->str, " %s.%s", quote_identifier(namespace), + quote_identifier(objectname)); + else if (objectname != NULL) + appendStringInfo(es->str, " %s", quote_identifier(objectname)); + if (objectname == NULL || strcmp(refname, objectname) != 0) + appendStringInfo(es->str, " %s", quote_identifier(refname)); + } + else + { + if (objectname != NULL) + ExplainPropertyText("Relation Name", objectname, es); + if (namespace != NULL) + ExplainPropertyText("Schema", namespace, es); + ExplainPropertyText("Alias", refname, es); + } +} + +/* + * ExplainCtidScan - A method of CustomPlanState; that shows extra info + * on EXPLAIN command. + */ +static void +ExplainCtidScan(CustomPlanState *node, List *ancestors, ExplainState *es) +{ + CtidScanState *ctss = (CtidScanState *) node; + CtidScanPlan *ctid_plan = (CtidScanPlan *) ctss->cps.ss.ps.plan; + + /* logic copied from show_qual and show_expression */ + if (ctid_plan->ctid_quals) + { + bool useprefix = es->verbose; + Node *qual; + List *context; + char *exprstr; + + /* Convert AND list to explicit AND */ + qual = (Node *) make_ands_explicit(ctid_plan->ctid_quals); + + /* Set up deparsing context */ + context = deparse_context_for_planstate((Node *)&node->ss.ps, + ancestors, + es->rtable, + es->rtable_names); + + /* Deparse the expression */ + exprstr = deparse_expression(qual, context, useprefix, false); + + /* And add to es->str */ + ExplainPropertyText("ctid quals", exprstr, es); + } +} + +/* + * ExplainCtidPreScanNode - A method of CustomPlanState; that informs + * the core backend relation's rtindex to be referenced, prior to the + * main EXPLAIN processing. + */ +static void +ExplainCtidPreScanNode(CustomPlanState *node, Bitmapset **rels_used) +{ + CtidScanState *ctss = (CtidScanState *) node; + Index scanrelid = ((Scan *)ctss->cps.ss.ps.plan)->scanrelid; + + *rels_used = bms_add_member(*rels_used, scanrelid); +} + +/* + * Entrypoint of this extension + */ +Datum +CtidScanAddPath(PG_FUNCTION_ARGS) +{ + customScanArg *cscan_arg = (customScanArg *)PG_GETARG_POINTER(0); + PlannerInfo *root; + RangeTblEntry *rte; + RelOptInfo *baserel; + char relkind; + ListCell *lc; + List *ctid_quals = NIL; + + if (cscan_arg->custom_class != CUSTOMPLAN_CLASS_SCAN) + PG_RETURN_VOID(); + + root = cscan_arg->root; + rte = cscan_arg->rte; + baserel = cscan_arg->baserel; + + /* all we can support is regular relations */ + if (rte->rtekind != RTE_RELATION) + PG_RETURN_VOID(); + + relkind = get_rel_relkind(rte->relid); + if (relkind != RELKIND_RELATION && + relkind != RELKIND_MATVIEW && + relkind != RELKIND_TOASTVALUE) + PG_RETURN_VOID(); + + /* walk on the restrict info */ + foreach (lc, baserel->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + List *temp; + + if (!IsA(rinfo, RestrictInfo)) + continue; /* probably should never happen */ + temp = CTidQualFromExpr((Node *) rinfo->clause, baserel->relid); + ctid_quals = list_concat(ctid_quals, temp); + } + + /* + * OK, it is case when a part of restriction clause makes sense to + * reduce number of tuples, so we will add a custom scan path being + * provided by this module. + */ + if (ctid_quals != NIL) + { + CtidScanPath *ctid_path; + Relids required_outer; + + /* + * We don't support pushing join clauses into the quals of a ctidscan, + * but it could still have required parameterization due to LATERAL + * refs in its tlist. + */ + required_outer = baserel->lateral_relids; + + ctid_path = palloc0(sizeof(CtidScanPath)); + ctid_path->cpath.path.type = T_CustomPath; + ctid_path->cpath.path.pathtype = T_CustomPlan; + ctid_path->cpath.path.parent = baserel; + ctid_path->cpath.path.param_info + = get_baserel_parampathinfo(root, baserel, required_outer); + ctid_path->cpath.methods = &ctidscan_path_methods; + ctid_path->ctid_quals = ctid_quals; + + CTidEstimateCosts(root, baserel, ctid_path); + + add_path(baserel, &ctid_path->cpath.path); + } + PG_RETURN_VOID(); +} +PG_FUNCTION_INFO_V1(CtidScanAddPath); + +/* + * Entrypoint of this extension + */ +void +_PG_init(void) +{ + /* setup ctidscan_path_methods */ + ctidscan_path_methods.CustomName = "ctidscan"; + ctidscan_path_methods.CreateCustomPlan = CreateCtidScanPlan; + ctidscan_path_methods.TextOutCustomPath = TextOutCtidScanPath; + + /* setup ctidscan_plan_methods */ + ctidscan_plan_methods.CustomName = "ctidscan"; + ctidscan_plan_methods.InitCustomPlan = InitCtidScanPlan; + ctidscan_plan_methods.SetCustomPlanRef = SetCtidScanPlanRef; + ctidscan_plan_methods.SupportBackwardScan = SupportCtidBackwardScan; + ctidscan_plan_methods.FinalizeCustomPlan = FinalizeCtidScanPlan; + ctidscan_plan_methods.CreateCustomPlanState = CreateCtidScanState; + ctidscan_plan_methods.TextOutCustomPlan = TextOutCtidScanPlan; + ctidscan_plan_methods.CopyCustomPlan = CopyCtidScanPlan; + + /* setup ctidscan_planstate_methods */ + ctidscan_exec_methods.CustomName = "ctidscan"; + ctidscan_exec_methods.BeginCustomPlan = BeginCtidScan; + ctidscan_exec_methods.ExecCustomPlan = ExecCtidScan; + ctidscan_exec_methods.EndCustomPlan = EndCtidScan; + ctidscan_exec_methods.ReScanCustomPlan = ReScanCtidScan; + ctidscan_exec_methods.MarkPosCustomPlan = NULL; + ctidscan_exec_methods.RestrPosCustomPlan = NULL; + ctidscan_exec_methods.ExplainCustomPlanTargetRel = ExplanCtidScanTargetRel; + ctidscan_exec_methods.ExplainCustomPlan = ExplainCtidScan; + ctidscan_exec_methods.ExplainCustomPreScanNode = ExplainCtidPreScanNode; + ctidscan_exec_methods.GetSpecialCustomVar = NULL; +} diff --git a/contrib/ctidscan/ctidscan.control b/contrib/ctidscan/ctidscan.control new file mode 100644 index 0000000..ad63432 --- /dev/null +++ b/contrib/ctidscan/ctidscan.control @@ -0,0 +1,5 @@ +# ctidscan extension +comment = 'example implementation for custom-plan interface' +default_version = '1.0' +module_pathname = '$libdir/ctidscan' +relocatable = true diff --git a/contrib/ctidscan/expected/ctidscan.out b/contrib/ctidscan/expected/ctidscan.out new file mode 100644 index 0000000..ae9c628 --- /dev/null +++ b/contrib/ctidscan/expected/ctidscan.out @@ -0,0 +1,294 @@ +-- +-- Regression Tests for Custom Plan APIs +-- +CREATE EXTENSION ctidscan; +-- construction of test data +SET client_min_messages TO 'warning'; +CREATE SCHEMA regtest_custom_scan; +SET search_path TO regtest_custom_scan, public; +CREATE TABLE t1 ( + a int primary key, + b text +); +INSERT INTO t1 (SELECT s, md5(s::text) FROM generate_series(1,400) s); +VACUUM ANALYZE t1; +CREATE TABLE t2 ( + x int primary key, + y text +); +INSERT INTO t2 (SELECT s, md5(s::text)||md5(s::text) FROM generate_series(1,400) s); +VACUUM ANALYZE t2; +RESET client_min_messages; +-- +-- Check Plans if no special extension is loaded. +-- +EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40; + QUERY PLAN +-------------------------------- + Index Scan using t1_pkey on t1 + Index Cond: (a = 40) +(2 rows) + +EXPLAIN (costs off) SELECT * FROM t1 WHERE b like '%789%'; + QUERY PLAN +-------------------------------- + Seq Scan on t1 + Filter: (b ~~ '%789%'::text) +(2 rows) + +EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid = '(2,10)'::tid; + QUERY PLAN +------------------------------------ + Tid Scan on t1 + TID Cond: (ctid = '(2,10)'::tid) +(2 rows) + +EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid; + QUERY PLAN +---------------------------------------------------------------------- + Custom (ctidscan) on t1 + Filter: ((ctid >= '(2,115)'::tid) AND (ctid <= '(3,10)'::tid)) + ctid quals: ((ctid >= '(2,115)'::tid) AND (ctid <= '(3,10)'::tid)) +(3 rows) + +EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40; + QUERY PLAN +-------------------------------- + Index Scan using t1_pkey on t1 + Index Cond: (a = 40) +(2 rows) + +EXPLAIN (costs off) SELECT * FROM t1 WHERE b like '%789%'; + QUERY PLAN +-------------------------------- + Seq Scan on t1 + Filter: (b ~~ '%789%'::text) +(2 rows) + +EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid = '(2,10)'::tid; + QUERY PLAN +------------------------------------ + Tid Scan on t1 + TID Cond: (ctid = '(2,10)'::tid) +(2 rows) + +EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid; + QUERY PLAN +---------------------------------------------------------------------- + Custom (ctidscan) on t1 + Filter: ((ctid >= '(2,115)'::tid) AND (ctid <= '(3,10)'::tid)) + ctid quals: ((ctid >= '(2,115)'::tid) AND (ctid <= '(3,10)'::tid)) +(3 rows) + +EXPLAIN (costs off) SELECT * FROM t1 JOIN t2 ON t1.ctid = t2.ctid WHERE t1.ctid < '(2,10)'::tid AND t2.ctid > '(1,75)'::tid; + QUERY PLAN +-------------------------------------------------- + Merge Join + Merge Cond: (t1.ctid = t2.ctid) + -> Sort + Sort Key: t1.ctid + -> Custom (ctidscan) on t1 + Filter: (ctid < '(2,10)'::tid) + ctid quals: (ctid < '(2,10)'::tid) + -> Sort + Sort Key: t2.ctid + -> Custom (ctidscan) on t2 + Filter: (ctid > '(1,75)'::tid) + ctid quals: (ctid > '(1,75)'::tid) +(12 rows) + +SELECT ctid,* FROM t1 WHERE ctid < '(1,20)'::tid; + ctid | a | b +---------+-----+---------------------------------- + (0,1) | 1 | c4ca4238a0b923820dcc509a6f75849b + (0,2) | 2 | c81e728d9d4c2f636f067f89cc14862c + (0,3) | 3 | eccbc87e4b5ce2fe28308fd9f2a7baf3 + (0,4) | 4 | a87ff679a2f3e71d9181a67b7542122c + (0,5) | 5 | e4da3b7fbbce2345d7772b0674a318d5 + (0,6) | 6 | 1679091c5a880faf6fb5e6087eb1b2dc + (0,7) | 7 | 8f14e45fceea167a5a36dedd4bea2543 + (0,8) | 8 | c9f0f895fb98ab9159f51fd0297e236d + (0,9) | 9 | 45c48cce2e2d7fbdea1afc51c7c6ad26 + (0,10) | 10 | d3d9446802a44259755d38e6d163e820 + (0,11) | 11 | 6512bd43d9caa6e02c990b0a82652dca + (0,12) | 12 | c20ad4d76fe97759aa27a0c99bff6710 + (0,13) | 13 | c51ce410c124a10e0db5e4b97fc2af39 + (0,14) | 14 | aab3238922bcc25a6f606eb525ffdc56 + (0,15) | 15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3 + (0,16) | 16 | c74d97b01eae257e44aa9d5bade97baf + (0,17) | 17 | 70efdf2ec9b086079795c442636b55fb + (0,18) | 18 | 6f4922f45568161a8cdf4ad2299f6d23 + (0,19) | 19 | 1f0e3dad99908345f7439f8ffabdffc4 + (0,20) | 20 | 98f13708210194c475687be6106a3b84 + (0,21) | 21 | 3c59dc048e8850243be8079a5c74d079 + (0,22) | 22 | b6d767d2f8ed5d21a44b0e5886680cb9 + (0,23) | 23 | 37693cfc748049e45d87b8c7d8b9aacd + (0,24) | 24 | 1ff1de774005f8da13f42943881c655f + (0,25) | 25 | 8e296a067a37563370ded05f5a3bf3ec + (0,26) | 26 | 4e732ced3463d06de0ca9a15b6153677 + (0,27) | 27 | 02e74f10e0327ad868d138f2b4fdd6f0 + (0,28) | 28 | 33e75ff09dd601bbe69f351039152189 + (0,29) | 29 | 6ea9ab1baa0efb9e19094440c317e21b + (0,30) | 30 | 34173cb38f07f89ddbebc2ac9128303f + (0,31) | 31 | c16a5320fa475530d9583c34fd356ef5 + (0,32) | 32 | 6364d3f0f495b6ab9dcf8d3b5c6e0b01 + (0,33) | 33 | 182be0c5cdcd5072bb1864cdee4d3d6e + (0,34) | 34 | e369853df766fa44e1ed0ff613f563bd + (0,35) | 35 | 1c383cd30b7c298ab50293adfecb7b18 + (0,36) | 36 | 19ca14e7ea6328a42e0eb13d585e4c22 + (0,37) | 37 | a5bfc9e07964f8dddeb95fc584cd965d + (0,38) | 38 | a5771bce93e200c36f7cd9dfd0e5deaa + (0,39) | 39 | d67d8ab4f4c10bf22aa353e27879133c + (0,40) | 40 | d645920e395fedad7bbbed0eca3fe2e0 + (0,41) | 41 | 3416a75f4cea9109507cacd8e2f2aefc + (0,42) | 42 | a1d0c6e83f027327d8461063f4ac58a6 + (0,43) | 43 | 17e62166fc8586dfa4d1bc0e1742c08b + (0,44) | 44 | f7177163c833dff4b38fc8d2872f1ec6 + (0,45) | 45 | 6c8349cc7260ae62e3b1396831a8398f + (0,46) | 46 | d9d4f495e875a2e075a1a4a6e1b9770f + (0,47) | 47 | 67c6a1e7ce56d3d6fa748ab6d9af3fd7 + (0,48) | 48 | 642e92efb79421734881b53e1e1b18b6 + (0,49) | 49 | f457c545a9ded88f18ecee47145a72c0 + (0,50) | 50 | c0c7c76d30bd3dcaefc96f40275bdc0a + (0,51) | 51 | 2838023a778dfaecdc212708f721b788 + (0,52) | 52 | 9a1158154dfa42caddbd0694a4e9bdc8 + (0,53) | 53 | d82c8d1619ad8176d665453cfb2e55f0 + (0,54) | 54 | a684eceee76fc522773286a895bc8436 + (0,55) | 55 | b53b3a3d6ab90ce0268229151c9bde11 + (0,56) | 56 | 9f61408e3afb633e50cdf1b20de6f466 + (0,57) | 57 | 72b32a1f754ba1c09b3695e0cb6cde7f + (0,58) | 58 | 66f041e16a60928b05a7e228a89c3799 + (0,59) | 59 | 093f65e080a295f8076b1c5722a46aa2 + (0,60) | 60 | 072b030ba126b2f4b2374f342be9ed44 + (0,61) | 61 | 7f39f8317fbdb1988ef4c628eba02591 + (0,62) | 62 | 44f683a84163b3523afe57c2e008bc8c + (0,63) | 63 | 03afdbd66e7929b125f8597834fa83a4 + (0,64) | 64 | ea5d2f1c4608232e07d3aa3d998e5135 + (0,65) | 65 | fc490ca45c00b1249bbe3554a4fdf6fb + (0,66) | 66 | 3295c76acbf4caaed33c36b1b5fc2cb1 + (0,67) | 67 | 735b90b4568125ed6c3f678819b6e058 + (0,68) | 68 | a3f390d88e4c41f2747bfa2f1b5f87db + (0,69) | 69 | 14bfa6bb14875e45bba028a21ed38046 + (0,70) | 70 | 7cbbc409ec990f19c78c75bd1e06f215 + (0,71) | 71 | e2c420d928d4bf8ce0ff2ec19b371514 + (0,72) | 72 | 32bb90e8976aab5298d5da10fe66f21d + (0,73) | 73 | d2ddea18f00665ce8623e36bd4e3c7c5 + (0,74) | 74 | ad61ab143223efbc24c7d2583be69251 + (0,75) | 75 | d09bf41544a3365a46c9077ebb5e35c3 + (0,76) | 76 | fbd7939d674997cdb4692d34de8633c4 + (0,77) | 77 | 28dd2c7955ce926456240b2ff0100bde + (0,78) | 78 | 35f4a8d465e6e1edc05f3d8ab658c551 + (0,79) | 79 | d1fe173d08e959397adf34b1d77e88d7 + (0,80) | 80 | f033ab37c30201f73f142449d037028d + (0,81) | 81 | 43ec517d68b6edd3015b3edc9a11367b + (0,82) | 82 | 9778d5d219c5080b9a6a17bef029331c + (0,83) | 83 | fe9fc289c3ff0af142b6d3bead98a923 + (0,84) | 84 | 68d30a9594728bc39aa24be94b319d21 + (0,85) | 85 | 3ef815416f775098fe977004015c6193 + (0,86) | 86 | 93db85ed909c13838ff95ccfa94cebd9 + (0,87) | 87 | c7e1249ffc03eb9ded908c236bd1996d + (0,88) | 88 | 2a38a4a9316c49e5a833517c45d31070 + (0,89) | 89 | 7647966b7343c29048673252e490f736 + (0,90) | 90 | 8613985ec49eb8f757ae6439e879bb2a + (0,91) | 91 | 54229abfcfa5649e7003b83dd4755294 + (0,92) | 92 | 92cc227532d17e56e07902b254dfad10 + (0,93) | 93 | 98dce83da57b0395e163467c9dae521b + (0,94) | 94 | f4b9ec30ad9f68f89b29639786cb62ef + (0,95) | 95 | 812b4ba287f5ee0bc9d43bbf5bbe87fb + (0,96) | 96 | 26657d5ff9020d2abefe558796b99584 + (0,97) | 97 | e2ef524fbf3d9fe611d5a8e90fefdc9c + (0,98) | 98 | ed3d2c21991e3bef5e069713af9fa6ca + (0,99) | 99 | ac627ab1ccbdb62ec96e702f07f6425b + (0,100) | 100 | f899139df5e1059396431415e770c6dd + (0,101) | 101 | 38b3eff8baf56627478ec76a704e9b52 + (0,102) | 102 | ec8956637a99787bd197eacd77acce5e + (0,103) | 103 | 6974ce5ac660610b44d9b9fed0ff9548 + (0,104) | 104 | c9e1074f5b3f9fc8ea15d152add07294 + (0,105) | 105 | 65b9eea6e1cc6bb9f0cd2a47751a186f + (0,106) | 106 | f0935e4cd5920aa6c7c996a5ee53a70f + (0,107) | 107 | a97da629b098b75c294dffdc3e463904 + (0,108) | 108 | a3c65c2974270fd093ee8a9bf8ae7d0b + (0,109) | 109 | 2723d092b63885e0d7c260cc007e8b9d + (0,110) | 110 | 5f93f983524def3dca464469d2cf9f3e + (0,111) | 111 | 698d51a19d8a121ce581499d7b701668 + (0,112) | 112 | 7f6ffaa6bb0b408017b62254211691b5 + (0,113) | 113 | 73278a4a86960eeb576a8fd4c9ec6997 + (0,114) | 114 | 5fd0b37cd7dbbb00f97ba6ce92bf5add + (0,115) | 115 | 2b44928ae11fb9384c4cf38708677c48 + (0,116) | 116 | c45147dee729311ef5b5c3003946c48f + (0,117) | 117 | eb160de1de89d9058fcb0b968dbbbd68 + (0,118) | 118 | 5ef059938ba799aaa845e1c2e8a762bd + (0,119) | 119 | 07e1cd7dca89a1678042477183b7ac3f + (0,120) | 120 | da4fb5c6e93e74d3df8527599fa62642 + (1,1) | 121 | 4c56ff4ce4aaf9573aa5dff913df997a + (1,2) | 122 | a0a080f42e6f13b3a2df133f073095dd + (1,3) | 123 | 202cb962ac59075b964b07152d234b70 + (1,4) | 124 | c8ffe9a587b126f152ed3d89a146b445 + (1,5) | 125 | 3def184ad8f4755ff269862ea77393dd + (1,6) | 126 | 069059b7ef840f0c74a814ec9237b6ec + (1,7) | 127 | ec5decca5ed3d6b8079e2e7e7bacc9f2 + (1,8) | 128 | 76dc611d6ebaafc66cc0879c71b5db5c + (1,9) | 129 | d1f491a404d6854880943e5c3cd9ca25 + (1,10) | 130 | 9b8619251a19057cff70779273e95aa6 + (1,11) | 131 | 1afa34a7f984eeabdbb0a7d494132ee5 + (1,12) | 132 | 65ded5353c5ee48d0b7d48c591b8f430 + (1,13) | 133 | 9fc3d7152ba9336a670e36d0ed79bc43 + (1,14) | 134 | 02522a2b2726fb0a03bb19f2d8d9524d + (1,15) | 135 | 7f1de29e6da19d22b51c68001e7e0e54 + (1,16) | 136 | 42a0e188f5033bc65bf8d78622277c4e + (1,17) | 137 | 3988c7f88ebcb58c6ce932b957b6f332 + (1,18) | 138 | 013d407166ec4fa56eb1e1f8cbe183b9 + (1,19) | 139 | e00da03b685a0dd18fb6a08af0923de0 +(139 rows) + +SELECT ctid,* FROM t1 WHERE ctid > '(4,0)'::tid; + ctid | a | b +------+---+--- +(0 rows) + +SELECT ctid,* FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid; + ctid | a | b +---------+-----+---------------------------------- + (2,115) | 355 | 82cec96096d4281b7c95cd7e74623496 + (2,116) | 356 | 6c524f9d5d7027454a783c841250ba71 + (2,117) | 357 | fb7b9ffa5462084c5f4e7e85a093e6d7 + (2,118) | 358 | aa942ab2bfa6ebda4840e7360ce6e7ef + (2,119) | 359 | c058f544c737782deacefa532d9add4c + (2,120) | 360 | e7b24b112a44fdd9ee93bdf998c6ca0e + (3,1) | 361 | 52720e003547c70561bf5e03b95aa99f + (3,2) | 362 | c3e878e27f52e2a57ace4d9a76fd9acf + (3,3) | 363 | 00411460f7c92d2124a67ea0f4cb5f85 + (3,4) | 364 | bac9162b47c56fc8a4d2a519803d51b3 + (3,5) | 365 | 9be40cee5b0eee1462c82c6964087ff9 + (3,6) | 366 | 5ef698cd9fe650923ea331c15af3b160 + (3,7) | 367 | 05049e90fa4f5039a8cadc6acbb4b2cc + (3,8) | 368 | cf004fdc76fa1a4f25f62e0eb5261ca3 + (3,9) | 369 | 0c74b7f78409a4022a2c4c5a5ca3ee19 + (3,10) | 370 | d709f38ef758b5066ef31b18039b8ce5 +(16 rows) + +SELECT t1.ctid,* FROM t1 JOIN t2 ON t1.ctid = t2.ctid WHERE t1.ctid < '(2,10)'::tid AND t2.ctid > '(1,75)'::tid; + ctid | a | b | x | y +--------+-----+----------------------------------+-----+------------------------------------------------------------------ + (1,76) | 196 | 084b6fbb10729ed4da8c3d3f5a3ae7c9 | 157 | 6c4b761a28b734fe93831e3fb400ce876c4b761a28b734fe93831e3fb400ce87 + (1,77) | 197 | 85d8ce590ad8981ca2c8286f79f59954 | 158 | 06409663226af2f3114485aa4e0a23b406409663226af2f3114485aa4e0a23b4 + (1,78) | 198 | 0e65972dce68dad4d52d063967f0a705 | 159 | 140f6969d5213fd0ece03148e62e461e140f6969d5213fd0ece03148e62e461e + (1,79) | 199 | 84d9ee44e457ddef7f2c4f25dc8fa865 | 160 | b73ce398c39f506af761d2277d853a92b73ce398c39f506af761d2277d853a92 + (1,80) | 200 | 3644a684f98ea8fe223c713b77189a77 | 161 | bd4c9ab730f5513206b999ec0d90d1fbbd4c9ab730f5513206b999ec0d90d1fb + (1,81) | 201 | 757b505cfd34c64c85ca5b5690ee5293 | 162 | 82aa4b0af34c2313a562076992e50aa382aa4b0af34c2313a562076992e50aa3 + (2,1) | 241 | f340f1b1f65b6df5b5e3f94d95b11daf | 163 | 0777d5c17d4066b82ab86dff8a46af6f0777d5c17d4066b82ab86dff8a46af6f + (2,2) | 242 | e4a6222cdb5b34375400904f03d8e6a5 | 164 | fa7cdfad1a5aaf8370ebeda47a1ff1c3fa7cdfad1a5aaf8370ebeda47a1ff1c3 + (2,3) | 243 | cb70ab375662576bd1ac5aaf16b3fca4 | 165 | 9766527f2b5d3e95d4a733fcfb77bd7e9766527f2b5d3e95d4a733fcfb77bd7e + (2,4) | 244 | 9188905e74c28e489b44e954ec0b9bca | 166 | 7e7757b1e12abcb736ab9a754ffb617a7e7757b1e12abcb736ab9a754ffb617a + (2,5) | 245 | 0266e33d3f546cb5436a10798e657d97 | 167 | 5878a7ab84fb43402106c575658472fa5878a7ab84fb43402106c575658472fa + (2,6) | 246 | 38db3aed920cf82ab059bfccbd02be6a | 168 | 006f52e9102a8d3be2fe5614f42ba989006f52e9102a8d3be2fe5614f42ba989 + (2,7) | 247 | 3cec07e9ba5f5bb252d13f5f431e4bbb | 169 | 3636638817772e42b59d74cff571fbb33636638817772e42b59d74cff571fbb3 + (2,8) | 248 | 621bf66ddb7c962aa0d22ac97d69b793 | 170 | 149e9677a5989fd342ae44213df68868149e9677a5989fd342ae44213df68868 + (2,9) | 249 | 077e29b11be80ab57e1a2ecabb7da330 | 171 | a4a042cf4fd6bfb47701cbc8a1653adaa4a042cf4fd6bfb47701cbc8a1653ada +(15 rows) + +-- Test cleanup +DROP SCHEMA regtest_custom_scan CASCADE; +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to table t1 +drop cascades to table t2 diff --git a/contrib/ctidscan/sql/ctidscan.sql b/contrib/ctidscan/sql/ctidscan.sql new file mode 100644 index 0000000..9759065 --- /dev/null +++ b/contrib/ctidscan/sql/ctidscan.sql @@ -0,0 +1,50 @@ +-- +-- Regression Tests for Custom Plan APIs +-- + +CREATE EXTENSION ctidscan; + +-- construction of test data +SET client_min_messages TO 'warning'; + +CREATE SCHEMA regtest_custom_scan; + +SET search_path TO regtest_custom_scan, public; + +CREATE TABLE t1 ( + a int primary key, + b text +); +INSERT INTO t1 (SELECT s, md5(s::text) FROM generate_series(1,400) s); +VACUUM ANALYZE t1; + +CREATE TABLE t2 ( + x int primary key, + y text +); +INSERT INTO t2 (SELECT s, md5(s::text)||md5(s::text) FROM generate_series(1,400) s); +VACUUM ANALYZE t2; + +RESET client_min_messages; +-- +-- Check Plans if no special extension is loaded. +-- +EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40; +EXPLAIN (costs off) SELECT * FROM t1 WHERE b like '%789%'; +EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid = '(2,10)'::tid; +EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid; + +EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40; +EXPLAIN (costs off) SELECT * FROM t1 WHERE b like '%789%'; +EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid = '(2,10)'::tid; +EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid; +EXPLAIN (costs off) SELECT * FROM t1 JOIN t2 ON t1.ctid = t2.ctid WHERE t1.ctid < '(2,10)'::tid AND t2.ctid > '(1,75)'::tid; + +SELECT ctid,* FROM t1 WHERE ctid < '(1,20)'::tid; +SELECT ctid,* FROM t1 WHERE ctid > '(4,0)'::tid; +SELECT ctid,* FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid; +SELECT t1.ctid,* FROM t1 JOIN t2 ON t1.ctid = t2.ctid WHERE t1.ctid < '(2,10)'::tid AND t2.ctid > '(1,75)'::tid; + + +-- Test cleanup +DROP SCHEMA regtest_custom_scan CASCADE; diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index b4a06e4..4742231 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -109,6 +109,11 @@ + pg_custom_plan + custom plan providers + + + pg_conversion encoding conversion information @@ -2508,6 +2513,60 @@ + + <structname>pg_custom_plan</structname> + + + pg_custom_plan + + + + The catalog pg_custom_plan describes + custom-plan providers. See + for more information. + + + + <structname>pg_custom_plan</> Columns + + + + Name + Type + References + Description + + + + + oid + oid + + Row identifier (hidden attribute; must be explicitly selected) + + + custname + name + + custom-plan name + + + custclass + char + + class of plan node on which custom-plna performs + + + custprovider + regproc + pg_proc.oid + custom-plan provder function + + + +
+
+ <structname>pg_database</structname> diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml index 1b0962c..f1b87f0 100644 --- a/doc/src/sgml/ref/allfiles.sgml +++ b/doc/src/sgml/ref/allfiles.sgml @@ -55,6 +55,7 @@ Complete list of usable sgml source files in this directory. + @@ -95,6 +96,7 @@ Complete list of usable sgml source files in this directory. + diff --git a/doc/src/sgml/ref/create_custom_plan.sgml b/doc/src/sgml/ref/create_custom_plan.sgml new file mode 100644 index 0000000..c7294c8 --- /dev/null +++ b/doc/src/sgml/ref/create_custom_plan.sgml @@ -0,0 +1,135 @@ + + + + + CREATE CUSTOM PLAN + + + + CREATE CUSTOM PLAN + 7 + SQL - Language Statements + + + + CREATE CUSTOM PLAN + define a new custom plan provider + + + + +CREATE CUSTOM PLAN provider_name FOR custom_class + PROVIDER provider_function + + + + + Description + + + CREATE CUSTOM PLAN 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 planenr alternative options + to scan relation, or potentially join relations and so on, in addition + to the built-in logics. It is usually extension modules that implements + callbacks according to the custom-plan interface. + + + 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 + + + + provider_name + + + The name of the custom-plan provider to be created. + + + + + + custom_class + + + Workload type on which custom-plan provider can perform. + Only scan is supported option right now. + + + + + + provider_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 ctidscan FOR scan PROVIDER 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.sgml b/doc/src/sgml/ref/drop_custom_plan.sgml new file mode 100644 index 0000000..b72146b --- /dev/null +++ b/doc/src/sgml/ref/drop_custom_plan.sgml @@ -0,0 +1,108 @@ + + + + + DROP CUSTOM PLAN + + + + DROP CUSTOM PLAN + 7 + SQL - Language Statements + + + + DROP CUSTOM PLAN + remove a custom-plan provider + + + + +DROP CUSTOM PLAN [ IF EXISTS ] name [ CASCADE | RESTRICT ] + + + + + Description + + + DROP CUSTOM PLAN 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 IF EXISTS foo; + + + + + Compatibility + + + There is no DROP CUSTOM PLAN command + in the SQL standard. + + + + + See Also + + + + + + + diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile index a974bd5..83b02e9 100644 --- a/src/backend/catalog/Makefile +++ b/src/backend/catalog/Makefile @@ -39,7 +39,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\ pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \ pg_ts_parser.h pg_ts_template.h pg_extension.h \ pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \ - pg_foreign_table.h \ + pg_foreign_table.h pg_custom_plan.h \ pg_default_acl.h pg_seclabel.h pg_shseclabel.h pg_collation.h pg_range.h \ toasting.h indexing.h \ ) diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index d41ba49..ec01170 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -30,6 +30,7 @@ #include "catalog/pg_constraint.h" #include "catalog/pg_conversion.h" #include "catalog/pg_conversion_fn.h" +#include "catalog/pg_custom_plan.h" #include "catalog/pg_database.h" #include "catalog/pg_default_acl.h" #include "catalog/pg_depend.h" @@ -154,7 +155,8 @@ static const Oid object_classes[MAX_OCLASS] = { UserMappingRelationId, /* OCLASS_USER_MAPPING */ DefaultAclRelationId, /* OCLASS_DEFACL */ ExtensionRelationId, /* OCLASS_EXTENSION */ - EventTriggerRelationId /* OCLASS_EVENT_TRIGGER */ + EventTriggerRelationId, /* OCLASS_EVENT_TRIGGER */ + CustomPlanRelationId, /* OCLASS_CUSTOM_PLAN */ }; @@ -1249,6 +1251,10 @@ doDeletion(const ObjectAddress *object, int flags) RemoveEventTriggerById(object->objectId); break; + case OCLASS_CUSTOM_PLAN: + RemoveCustomPlanById(object->objectId); + break; + default: elog(ERROR, "unrecognized object class: %u", object->classId); @@ -2316,6 +2322,9 @@ getObjectClass(const ObjectAddress *object) case EventTriggerRelationId: return OCLASS_EVENT_TRIGGER; + + case CustomPlanRelationId: + return OCLASS_CUSTOM_PLAN; } /* shouldn't get here */ diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c index c7c8f4b..2a5b5cf 100644 --- a/src/backend/catalog/objectaddress.c +++ b/src/backend/catalog/objectaddress.c @@ -30,6 +30,7 @@ #include "catalog/pg_collation.h" #include "catalog/pg_constraint.h" #include "catalog/pg_conversion.h" +#include "catalog/pg_custom_plan.h" #include "catalog/pg_database.h" #include "catalog/pg_extension.h" #include "catalog/pg_foreign_data_wrapper.h" @@ -152,6 +153,18 @@ static const ObjectPropertyType ObjectProperty[] = true }, { + CustomPlanRelationId, + CustomPlanOidIndexId, + CUSTOMPLANOID, + CUSTOMPLANNAME, + Anum_pg_custom_plan_custname, + InvalidAttrNumber, + InvalidAttrNumber, + InvalidAttrNumber, + -1, + false, + }, + { DatabaseRelationId, DatabaseOidIndexId, DATABASEOID, @@ -529,6 +542,7 @@ get_object_address(ObjectType objtype, List *objname, List *objargs, case OBJECT_FDW: case OBJECT_FOREIGN_SERVER: case OBJECT_EVENT_TRIGGER: + case OBJECT_CUSTOM_PLAN: address = get_object_address_unqualified(objtype, objname, missing_ok); break; @@ -755,6 +769,9 @@ get_object_address_unqualified(ObjectType objtype, case OBJECT_EVENT_TRIGGER: msg = gettext_noop("event trigger name cannot be qualified"); break; + case OBJECT_CUSTOM_PLAN: + msg = gettext_noop("custom plan name cannot be qualified"); + break; default: elog(ERROR, "unrecognized objtype: %d", (int) objtype); msg = NULL; /* placate compiler */ @@ -815,6 +832,11 @@ get_object_address_unqualified(ObjectType objtype, address.objectId = get_event_trigger_oid(name, missing_ok); address.objectSubId = 0; break; + case OBJECT_CUSTOM_PLAN: + address.classId = CustomPlanRelationId; + address.objectId = get_custom_plan_oid(name, missing_ok); + address.objectSubId = 0; + break; default: elog(ERROR, "unrecognized objtype: %d", (int) objtype); /* placate compiler, which doesn't know elog won't return */ @@ -1295,6 +1317,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address, break; case OBJECT_TSPARSER: case OBJECT_TSTEMPLATE: + case OBJECT_CUSTOM_PLAN: /* We treat these object types as being owned by superusers */ if (!superuser_arg(roleid)) ereport(ERROR, @@ -2166,6 +2189,21 @@ getObjectDescription(const ObjectAddress *object) break; } + case OCLASS_CUSTOM_PLAN: + { + HeapTuple tup; + + tup = SearchSysCache1(CUSTOMPLANOID, + ObjectIdGetDatum(object->objectId)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for custom-plan %u", + object->objectId); + appendStringInfo(&buffer, _("custom plan %s"), + NameStr(((Form_pg_custom_plan) GETSTRUCT(tup))->custname)); + ReleaseSysCache(tup); + break; + } + default: appendStringInfo(&buffer, "unrecognized object %u %u %d", object->classId, @@ -2577,6 +2615,10 @@ getObjectTypeDescription(const ObjectAddress *object) appendStringInfoString(&buffer, "event trigger"); break; + case OCLASS_CUSTOM_PLAN: + appendStringInfoString(&buffer, "custom plan"); + break; + default: appendStringInfo(&buffer, "unrecognized %u", object->classId); break; @@ -3330,6 +3372,23 @@ getObjectIdentity(const ObjectAddress *object) break; } + case OCLASS_CUSTOM_PLAN: + { + HeapTuple tup; + Form_pg_custom_plan custForm; + + tup = SearchSysCache1(CUSTOMPLANOID, + ObjectIdGetDatum(object->objectId)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for custom-plan %u", + object->objectId); + custForm = (Form_pg_custom_plan) GETSTRUCT(tup); + appendStringInfoString(&buffer, + quote_identifier(NameStr(custForm->custname))); + ReleaseSysCache(tup); + break; + } + default: appendStringInfo(&buffer, "unrecognized object %u %u %d", object->classId, diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile index 22f116b..1e8e6f4 100644 --- a/src/backend/commands/Makefile +++ b/src/backend/commands/Makefile @@ -14,7 +14,7 @@ include $(top_builddir)/src/Makefile.global OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o \ collationcmds.o constraint.o conversioncmds.o copy.o createas.o \ - dbcommands.o define.o discard.o dropcmds.o \ + custom_plan.o dbcommands.o define.o discard.o dropcmds.o \ event_trigger.o explain.o extension.o foreigncmds.o functioncmds.o \ indexcmds.o lockcmds.o matview.o operatorcmds.o opclasscmds.o \ portalcmds.o prepare.o proclang.o \ diff --git a/src/backend/commands/custom_plan.c b/src/backend/commands/custom_plan.c new file mode 100644 index 0000000..27cd75d --- /dev/null +++ b/src/backend/commands/custom_plan.c @@ -0,0 +1,186 @@ +/*------------------------------------------------------------------------- + * + * custom_plan.c + * custom plan nodes creation/manipulation commands + * + * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/commands/custom_plan.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" +#include "access/genam.h" +#include "access/heapam.h" +#include "access/htup_details.h" +#include "catalog/dependency.h" +#include "catalog/indexing.h" +#include "catalog/objectaccess.h" +#include "catalog/pg_custom_plan.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_type.h" +#include "commands/defrem.h" +#include "fmgr.h" +#include "miscadmin.h" +#include "parser/parse_func.h" +#include "utils/builtins.h" +#include "utils/inval.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/rel.h" +#include "utils/syscache.h" + +/* + * utility function to lookup a custom-plan provider by name + */ +Oid +get_custom_plan_oid(const char *custom_name, bool missing_ok) +{ + Oid cust_oid; + + cust_oid = GetSysCacheOid1(CUSTOMPLANNAME, CStringGetDatum(custom_name)); + if (!OidIsValid(cust_oid) && !missing_ok) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("custom-plan provider \"%s\" does not exist", + custom_name))); + return cust_oid; +} + +/* + * Drop a custom-plan provider + */ +void +RemoveCustomPlanById(Oid cust_oid) +{ + Relation rel; + HeapTuple tuple; + + rel = heap_open(CustomPlanRelationId, RowExclusiveLock); + + tuple = SearchSysCache1(CUSTOMPLANOID, ObjectIdGetDatum(cust_oid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for custom-plan provider %u", + cust_oid); + + simple_heap_delete(rel, &tuple->t_self); + + ReleaseSysCache(tuple); + + heap_close(rel, RowExclusiveLock); +} + +/* + * Create a custom-plan provider + */ +Oid +DefineCustomPlan(CreateCustomPlanStmt *stmt) +{ + Relation rel; + Oid cust_oid; + Oid cust_provider = InvalidOid; + Datum values[Natts_pg_custom_plan]; + bool isnull[Natts_pg_custom_plan]; + HeapTuple tuple; + ListCell *cell; + ObjectAddress myself; + ObjectAddress referenced; + + rel = heap_open(CustomPlanRelationId, RowExclusiveLock); + + /* must be super user */ + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to create custom-plan provider \"%s\"", + stmt->custom_name), + errhint("Must be superuser to create a custom-plan node."))); + + /* check namespace conflicts */ + cust_oid = get_custom_plan_oid(stmt->custom_name, true); + if (OidIsValid(cust_oid)) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("custom-plan provider \"%s\" already exists", + stmt->custom_name))); + + /* check custom-plan class */ + if (stmt->custom_class != CUSTOMPLAN_CLASS_SCAN) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("unexpected custom plan class specified: %d", + (int)stmt->custom_class))); + + /* parse custom-plan options */ + foreach (cell, stmt->custom_options) + { + DefElem *defel = lfirst(cell); + + Assert(IsA(defel, DefElem)); + + if (strcmp(defel->defname, "provider") == 0) + { + Oid argtypes[1]; + + if (OidIsValid(cust_provider)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + + argtypes[0] = INTERNALOID; + cust_provider = LookupFuncName((List *)defel->arg, + 1, argtypes, false); + if (get_func_rettype(cust_provider) != VOIDOID) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("function %s must return type \"void\"", + NameListToString((List *) defel->arg)))); + } + else + elog(ERROR, "unexpected custom-plan option: %s", + defel->defname); + } + + if (!OidIsValid(cust_provider)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("PROVIDER must be provided"))); + + /* + * Insert tuple into pg_custom_plan system catalog + */ + memset(values, 0, sizeof(values)); + memset(isnull, 0, sizeof(isnull)); + values[Anum_pg_custom_plan_custname - 1] + = DirectFunctionCall1(namein, CStringGetDatum(stmt->custom_name)); + values[Anum_pg_custom_plan_custclass - 1] + = stmt->custom_class; + values[Anum_pg_custom_plan_custprovider - 1] + = ObjectIdGetDatum(cust_provider); + + tuple = heap_form_tuple(RelationGetDescr(rel), values, isnull); + + cust_oid = simple_heap_insert(rel, tuple); + CatalogUpdateIndexes(rel, tuple); + + heap_freetuple(tuple); + + /* record dependencies */ + myself.classId = CustomPlanRelationId; + myself.objectId = cust_oid; + myself.objectSubId = 0; + + referenced.classId = ProcedureRelationId; + referenced.objectId = cust_provider; + referenced.objectSubId = 0; + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + + /* Post creation hook for new custom-plan provider */ + InvokeObjectPostCreateHook(CustomPlanRelationId, cust_oid, 0); + + heap_close(rel, RowExclusiveLock); + + return cust_oid; +} diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c index e64ad80..104ff17 100644 --- a/src/backend/commands/dropcmds.c +++ b/src/backend/commands/dropcmds.c @@ -408,6 +408,11 @@ does_not_exist_skipping(ObjectType objtype, List *objname, List *objargs) args = strVal(linitial(objargs)); } break; + case OBJECT_CUSTOM_PLAN: + msg = gettext_noop("custom-plan \"%s\" does not exist, skipping"); + name = NameListToString(objname); + break; + default: elog(ERROR, "unexpected object type (%d)", (int) objtype); break; diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c index 6d4e091..a3468f3 100644 --- a/src/backend/commands/event_trigger.c +++ b/src/backend/commands/event_trigger.c @@ -923,6 +923,7 @@ EventTriggerSupportsObjectType(ObjectType obtype) case OBJECT_CONSTRAINT: case OBJECT_COLLATION: case OBJECT_CONVERSION: + case OBJECT_CUSTOM_PLAN: case OBJECT_DOMAIN: case OBJECT_EXTENSION: case OBJECT_FDW: @@ -975,6 +976,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass) case OCLASS_COLLATION: case OCLASS_CONSTRAINT: case OCLASS_CONVERSION: + case OCLASS_CUSTOM_PLAN: case OCLASS_DEFAULT: case OCLASS_LANGUAGE: case OCLASS_LARGEOBJECT: diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 0d9663c..450a123 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -722,6 +722,14 @@ ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used) *rels_used = bms_add_member(*rels_used, ((Scan *) plan)->scanrelid); break; + case T_CustomPlan: + case T_CustomPlanMarkPos: + { + CustomPlanState *cps = (CustomPlanState *)planstate; + + cps->methods->ExplainCustomPreScanNode(cps, rels_used); + } + break; case T_ModifyTable: /* cf ExplainModifyTarget */ *rels_used = bms_add_member(*rels_used, @@ -848,6 +856,7 @@ ExplainNode(PlanState *planstate, List *ancestors, const char *sname; /* node type name for non-text output */ const char *strategy = NULL; const char *operation = NULL; + const char *custom_name = NULL; int save_indent = es->indent; bool haschildren; @@ -936,6 +945,14 @@ ExplainNode(PlanState *planstate, List *ancestors, case T_ForeignScan: pname = sname = "Foreign Scan"; break; + case T_CustomPlan: + sname = "Custom"; + custom_name = ((CustomPlan *) plan)->methods->CustomName; + if (custom_name) + pname = psprintf("Custom (%s)", custom_name); + else + pname = sname; + break; case T_Material: pname = sname = "Materialize"; break; @@ -1037,6 +1054,8 @@ ExplainNode(PlanState *planstate, List *ancestors, ExplainPropertyText("Parent Relationship", relationship, es); if (plan_name) ExplainPropertyText("Subplan Name", plan_name, es); + if (custom_name) + ExplainPropertyText("Custom", custom_name, es); } switch (nodeTag(plan)) @@ -1084,6 +1103,13 @@ ExplainNode(PlanState *planstate, List *ancestors, ExplainPropertyText("Index Name", indexname, es); } break; + case T_CustomPlan: + { + CustomPlanState *cps = (CustomPlanState *)planstate; + + cps->methods->ExplainCustomPlanTargetRel(cps, es); + } + break; case T_ModifyTable: ExplainModifyTarget((ModifyTable *) plan, es); break; @@ -1353,6 +1379,17 @@ ExplainNode(PlanState *planstate, List *ancestors, planstate, es); show_foreignscan_info((ForeignScanState *) planstate, es); break; + case T_CustomPlan: + { + CustomPlanState *cps = (CustomPlanState *) planstate; + + show_scan_qual(plan->qual, "Filter", planstate, ancestors, es); + if (plan->qual) + show_instrumentation_count("Rows Removed by Filter", 1, + planstate, es); + cps->methods->ExplainCustomPlan(cps, ancestors, es); + } + break; case T_NestLoop: show_upper_qual(((NestLoop *) plan)->join.joinqual, "Join Filter", planstate, ancestors, es); diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile index 6081b56..af707b0 100644 --- a/src/backend/executor/Makefile +++ b/src/backend/executor/Makefile @@ -16,7 +16,7 @@ OBJS = execAmi.o execCurrent.o execGrouping.o execJunk.o execMain.o \ execProcnode.o execQual.o execScan.o execTuples.o \ execUtils.o functions.o instrument.o nodeAppend.o nodeAgg.o \ nodeBitmapAnd.o nodeBitmapOr.o \ - nodeBitmapHeapscan.o nodeBitmapIndexscan.o nodeHash.o \ + nodeBitmapHeapscan.o nodeBitmapIndexscan.o nodeCustom.o nodeHash.o \ nodeHashjoin.o nodeIndexscan.o nodeIndexonlyscan.o \ nodeLimit.o nodeLockRows.o \ nodeMaterial.o nodeMergeAppend.o nodeMergejoin.o nodeModifyTable.o \ diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c index 640964c..5fc9cbb 100644 --- a/src/backend/executor/execAmi.c +++ b/src/backend/executor/execAmi.c @@ -21,6 +21,7 @@ #include "executor/nodeBitmapIndexscan.h" #include "executor/nodeBitmapOr.h" #include "executor/nodeCtescan.h" +#include "executor/nodeCustom.h" #include "executor/nodeForeignscan.h" #include "executor/nodeFunctionscan.h" #include "executor/nodeGroup.h" @@ -197,6 +198,10 @@ ExecReScan(PlanState *node) ExecReScanForeignScan((ForeignScanState *) node); break; + case T_CustomPlanState: + ExecReScanCustomPlan((CustomPlanState *) node); + break; + case T_NestLoopState: ExecReScanNestLoop((NestLoopState *) node); break; @@ -291,6 +296,10 @@ ExecMarkPos(PlanState *node) ExecValuesMarkPos((ValuesScanState *) node); break; + case T_CustomPlanState: + ExecCustomMarkPos((CustomPlanState *) node); + break; + case T_MaterialState: ExecMaterialMarkPos((MaterialState *) node); break; @@ -348,6 +357,10 @@ ExecRestrPos(PlanState *node) ExecValuesRestrPos((ValuesScanState *) node); break; + case T_CustomPlanState: + ExecCustomRestrPos((CustomPlanState *) node); + break; + case T_MaterialState: ExecMaterialRestrPos((MaterialState *) node); break; @@ -390,6 +403,7 @@ ExecSupportsMarkRestore(NodeTag plantype) case T_ValuesScan: case T_Material: case T_Sort: + case T_CustomPlanMarkPos: return true; case T_Result: @@ -465,6 +479,15 @@ ExecSupportsBackwardScan(Plan *node) return ExecSupportsBackwardScan(((SubqueryScan *) node)->subplan) && TargetListSupportsBackwardScan(node->targetlist); + case T_CustomPlan: + { + CustomPlan *cplan = (CustomPlan *) node; + + if (cplan->methods->SupportBackwardScan) + return cplan->methods->SupportBackwardScan(cplan); + } + return false; + case T_Material: case T_Sort: /* these don't evaluate tlist */ diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c index c0189eb..62ebab9 100644 --- a/src/backend/executor/execProcnode.c +++ b/src/backend/executor/execProcnode.c @@ -85,6 +85,7 @@ #include "executor/nodeBitmapIndexscan.h" #include "executor/nodeBitmapOr.h" #include "executor/nodeCtescan.h" +#include "executor/nodeCustom.h" #include "executor/nodeForeignscan.h" #include "executor/nodeFunctionscan.h" #include "executor/nodeGroup.h" @@ -244,6 +245,12 @@ ExecInitNode(Plan *node, EState *estate, int eflags) estate, eflags); break; + case T_CustomPlan: + case T_CustomPlanMarkPos: + result = (PlanState *) ExecInitCustomPlan((CustomPlan *) node, + estate, eflags); + break; + /* * join nodes */ @@ -442,6 +449,10 @@ ExecProcNode(PlanState *node) result = ExecForeignScan((ForeignScanState *) node); break; + case T_CustomPlanState: + result = ExecCustomPlan((CustomPlanState *) node); + break; + /* * join nodes */ @@ -558,6 +569,10 @@ MultiExecProcNode(PlanState *node) result = MultiExecBitmapOr((BitmapOrState *) node); break; + case T_CustomPlanState: + result = MultiExecCustomPlan((CustomPlanState *) node); + break; + default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); result = NULL; @@ -678,6 +693,10 @@ ExecEndNode(PlanState *node) ExecEndForeignScan((ForeignScanState *) node); break; + case T_CustomPlanState: + ExecEndCustomPlan((CustomPlanState *) node); + break; + /* * join nodes */ diff --git a/src/backend/executor/nodeCustom.c b/src/backend/executor/nodeCustom.c new file mode 100644 index 0000000..55502fd --- /dev/null +++ b/src/backend/executor/nodeCustom.c @@ -0,0 +1,135 @@ +/* ------------------------------------------------------------------------ + * + * nodeCustom.c + * Routines to handle execution of custom plan node + * + * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * ------------------------------------------------------------------------ + */ +#include "postgres.h" + +#include "executor/executor.h" +#include "executor/nodeCustom.h" +#include "nodes/execnodes.h" +#include "nodes/plannodes.h" +#include "parser/parsetree.h" +#include "utils/hsearch.h" +#include "utils/memutils.h" +#include "utils/rel.h" + +CustomPlanState * +ExecInitCustomPlan(CustomPlan *cplan, EState *estate, int eflags) +{ + CustomPlanState *cps = cplan->methods->CreateCustomPlanState(cplan); + + /* fill up fields of PlanState */ + cps->ss.ps.plan = &cplan->scan.plan; + cps->ss.ps.state = estate; + + /* create expression context for node */ + ExecAssignExprContext(estate, &cps->ss.ps); + cps->ss.ps.ps_TupFromTlist = false; + + /* initialize child expressions */ + cps->ss.ps.targetlist = (List *) + ExecInitExpr((Expr *) cplan->scan.plan.targetlist, + (PlanState *) cps); + cps->ss.ps.qual = (List *) + ExecInitExpr((Expr *) cplan->scan.plan.qual, + (PlanState *) cps); + + /* initialization of result tuple slot */ + ExecInitResultTupleSlot(estate, &cps->ss.ps); + ExecAssignResultTypeFromTL(&cps->ss.ps); + + if (cplan->scan.scanrelid > 0) + { + Relation heap_rel; + + heap_rel = ExecOpenScanRelation(estate, cplan->scan.scanrelid, eflags); + cps->ss.ss_currentRelation = heap_rel; + cps->ss.ss_currentScanDesc = NULL; /* set by provider on demand */ + ExecInitScanTupleSlot(estate, &cps->ss); + ExecAssignScanType(&cps->ss, RelationGetDescr(heap_rel)); + ExecAssignScanProjectionInfo(&cps->ss); + } + else + { + /* + * Elsewhere, custom-plan provider should be responsible to put + * appropriate initialization of scan tuple-slot and projection + * info. + */ + cps->ss.ss_currentRelation = NULL; + cps->ss.ss_currentScanDesc = NULL; + cps->ss.ss_ScanTupleSlot = NULL; + cps->ss.ps.ps_ProjInfo = NULL; + } + /* + * Then, custom-plan provider can have all the own original + * initialization on demand. + */ + cps->methods->BeginCustomPlan(cps, estate, eflags); + + return cps; +} + +TupleTableSlot * +ExecCustomPlan(CustomPlanState *cpstate) +{ + Assert(cpstate->methods->ExecCustomPlan != NULL); + return cpstate->methods->ExecCustomPlan(cpstate); +} + +Node * +MultiExecCustomPlan(CustomPlanState *cpstate) +{ + if (!cpstate->methods->MultiExecCustomPlan) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("CustomPlan \"%s\" does not support MultiExec method", + cpstate->methods->CustomName))); + return cpstate->methods->MultiExecCustomPlan(cpstate); +} + +void +ExecEndCustomPlan(CustomPlanState *cpstate) +{ + Assert(cpstate->methods->EndCustomPlan != NULL); + cpstate->methods->EndCustomPlan(cpstate); + + /* Free the exprcontext */ + ExecFreeExprContext(&cpstate->ss.ps); + + /* Clean out the tuple table */ + ExecClearTuple(cpstate->ss.ps.ps_ResultTupleSlot); + if (cpstate->ss.ss_ScanTupleSlot) + ExecClearTuple(cpstate->ss.ss_ScanTupleSlot); + + /* Close the heap relation, if needed */ + if (cpstate->ss.ss_currentRelation) + ExecCloseScanRelation(cpstate->ss.ss_currentRelation); +} + +void +ExecReScanCustomPlan(CustomPlanState *cpstate) +{ + Assert(cpstate->methods->ReScanCustomPlan != NULL); + cpstate->methods->ReScanCustomPlan(cpstate); +} + +void +ExecCustomMarkPos(CustomPlanState *cpstate) +{ + Assert(cpstate->methods->MarkPosCustomPlan != NULL); + cpstate->methods->MarkPosCustomPlan(cpstate); +} + +void +ExecCustomRestrPos(CustomPlanState *cpstate) +{ + Assert(cpstate->methods->RestrPosCustomPlan != NULL); + cpstate->methods->RestrPosCustomPlan(cpstate); +} diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 43530aa..a10599a 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -598,6 +598,21 @@ _copyForeignScan(const ForeignScan *from) } /* + * _copyCustomPlan + */ +static CustomPlan * +_copyCustomPlan(const CustomPlan *from) +{ + CustomPlan *newnode; + + newnode = from->methods->CopyCustomPlan(from); + Assert(nodeTag(newnode) == nodeTag(from)); + CopyScanFields((const Scan *) from, (Scan *) newnode); + + return newnode; +} + +/* * CopyJoinFields * * This function copies the fields of the Join node. It is used by @@ -3821,6 +3836,18 @@ _copyAlterTSConfigurationStmt(const AlterTSConfigurationStmt *from) return newnode; } +static CreateCustomPlanStmt * +_copyCreateCustomPlanStmt(const CreateCustomPlanStmt *from) +{ + CreateCustomPlanStmt *newnode = makeNode(CreateCustomPlanStmt); + + COPY_STRING_FIELD(custom_name); + COPY_SCALAR_FIELD(custom_class); + COPY_NODE_FIELD(custom_options); + + return newnode; +} + /* **************************************************************** * pg_list.h copy functions * **************************************************************** @@ -3984,6 +4011,10 @@ copyObject(const void *from) case T_ForeignScan: retval = _copyForeignScan(from); break; + case T_CustomPlan: + case T_CustomPlanMarkPos: + retval = _copyCustomPlan(from); + break; case T_Join: retval = _copyJoin(from); break; @@ -4530,6 +4561,9 @@ copyObject(const void *from) case T_AlterTSConfigurationStmt: retval = _copyAlterTSConfigurationStmt(from); break; + case T_CreateCustomPlanStmt: + retval = _copyCreateCustomPlanStmt(from); + break; case T_A_Expr: retval = _copyAExpr(from); diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 2407cb7..fc0165e 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1994,6 +1994,17 @@ _equalAlterTSConfigurationStmt(const AlterTSConfigurationStmt *a, } static bool +_equalCreateCustomPlanStmt(const CreateCustomPlanStmt *a, + const CreateCustomPlanStmt *b) +{ + COMPARE_STRING_FIELD(custom_name); + COMPARE_SCALAR_FIELD(custom_class); + COMPARE_NODE_FIELD(custom_options); + + return true; +} + +static bool _equalAExpr(const A_Expr *a, const A_Expr *b) { COMPARE_SCALAR_FIELD(kind); @@ -2998,6 +3009,9 @@ equal(const void *a, const void *b) case T_AlterTSConfigurationStmt: retval = _equalAlterTSConfigurationStmt(a, b); break; + case T_CreateCustomPlanStmt: + retval = _equalCreateCustomPlanStmt(a, b); + break; case T_A_Expr: retval = _equalAExpr(a, b); diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 11c7486..bf3953d 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -564,6 +564,22 @@ _outForeignScan(StringInfo str, const ForeignScan *node) } static void +_outCustomPlan(StringInfo str, const CustomPlan *node) +{ + if (IsA(node, CustomPlan)) + WRITE_NODE_TYPE("CUSTOMPLAN"); + else if (IsA(node, CustomPlanMarkPos)) + WRITE_NODE_TYPE("CUSTOMPLANMARKPOS"); + else + elog(ERROR, "unexpected node tag given: %d", (int)nodeTag(node)); + + _outScanInfo(str, (const Scan *) node); + appendStringInfo(str, " :methods"); + _outToken(str, node->methods->CustomName); + node->methods->TextOutCustomPlan(str, node); +} + +static void _outJoin(StringInfo str, const Join *node) { WRITE_NODE_TYPE("JOIN"); @@ -1581,6 +1597,16 @@ _outForeignPath(StringInfo str, const ForeignPath *node) } static void +_outCustomPath(StringInfo str, const CustomPath *node) +{ + WRITE_NODE_TYPE("CUSTOMPATH"); + _outPathInfo(str, (const Path *) node); + appendStringInfo(str, " :methods"); + _outToken(str, node->methods->CustomName); + node->methods->TextOutCustomPath(str, node); +} + +static void _outAppendPath(StringInfo str, const AppendPath *node) { WRITE_NODE_TYPE("APPENDPATH"); @@ -2829,6 +2855,10 @@ _outNode(StringInfo str, const void *obj) case T_ForeignScan: _outForeignScan(str, obj); break; + case T_CustomPlan: + case T_CustomPlanMarkPos: + _outCustomPlan(str, obj); + break; case T_Join: _outJoin(str, obj); break; @@ -3037,6 +3067,9 @@ _outNode(StringInfo str, const void *obj) case T_ForeignPath: _outForeignPath(str, obj); break; + case T_CustomPath: + _outCustomPath(str, obj); + break; case T_AppendPath: _outAppendPath(str, obj); break; diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index fdaa964..bf95408 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -326,7 +326,7 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, } break; case RTE_SUBQUERY: - /* Subquery --- fully handled during set_rel_size */ + /* Subquery --- path was added during set_rel_size */ break; case RTE_FUNCTION: /* RangeFunction */ @@ -337,12 +337,17 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, set_values_pathlist(root, rel, rte); break; case RTE_CTE: - /* CTE reference --- fully handled during set_rel_size */ + /* CTE reference --- path was added during set_rel_size */ break; default: elog(ERROR, "unexpected rtekind: %d", (int) rel->rtekind); break; } + /* Also, consider paths by custom-plan providers */ + call_custom_scan_providers(root, rel, rte); + + /* Select cheapest path */ + set_cheapest(rel); } #ifdef OPTIMIZER_DEBUG @@ -391,9 +396,6 @@ set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) /* Consider TID scans */ create_tidscan_paths(root, rel); - - /* Now find the cheapest of the paths for this rel */ - set_cheapest(rel); } /* @@ -419,9 +421,6 @@ set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) { /* Call the FDW's GetForeignPaths function to generate path(s) */ rel->fdwroutine->GetForeignPaths(root, rel, rte->relid); - - /* Select cheapest path */ - set_cheapest(rel); } /* @@ -1256,9 +1255,6 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel, /* Generate appropriate path */ add_path(rel, create_subqueryscan_path(root, rel, pathkeys, required_outer)); - - /* Select cheapest path (pretty easy in this case...) */ - set_cheapest(rel); } /* @@ -1327,9 +1323,6 @@ set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) /* Generate appropriate path */ add_path(rel, create_functionscan_path(root, rel, pathkeys, required_outer)); - - /* Select cheapest path (pretty easy in this case...) */ - set_cheapest(rel); } /* @@ -1350,9 +1343,6 @@ set_values_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) /* Generate appropriate path */ add_path(rel, create_valuesscan_path(root, rel, required_outer)); - - /* Select cheapest path (pretty easy in this case...) */ - set_cheapest(rel); } /* @@ -1419,9 +1409,6 @@ set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) /* Generate appropriate path */ add_path(rel, create_ctescan_path(root, rel, required_outer)); - - /* Select cheapest path (pretty easy in this case...) */ - set_cheapest(rel); } /* @@ -1472,9 +1459,6 @@ set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) /* Generate appropriate path */ add_path(rel, create_worktablescan_path(root, rel, required_outer)); - - /* Select cheapest path (pretty easy in this case...) */ - set_cheapest(rel); } /* diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 4b641a2..cb07af4 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -77,13 +77,13 @@ static WorkTableScan *create_worktablescan_plan(PlannerInfo *root, Path *best_pa List *tlist, List *scan_clauses); static ForeignScan *create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path, List *tlist, List *scan_clauses); +static Plan *create_custom_plan(PlannerInfo *root, CustomPath *best_path); static NestLoop *create_nestloop_plan(PlannerInfo *root, NestPath *best_path, Plan *outer_plan, Plan *inner_plan); static MergeJoin *create_mergejoin_plan(PlannerInfo *root, MergePath *best_path, Plan *outer_plan, Plan *inner_plan); static HashJoin *create_hashjoin_plan(PlannerInfo *root, HashPath *best_path, Plan *outer_plan, Plan *inner_plan); -static Node *replace_nestloop_params(PlannerInfo *root, Node *expr); static Node *replace_nestloop_params_mutator(Node *node, PlannerInfo *root); static void process_subquery_nestloop_params(PlannerInfo *root, List *subplan_params); @@ -261,6 +261,9 @@ create_plan_recurse(PlannerInfo *root, Path *best_path) plan = create_unique_plan(root, (UniquePath *) best_path); break; + case T_CustomPlan: + plan = create_custom_plan(root, (CustomPath *) best_path); + break; default: elog(ERROR, "unrecognized node type: %d", (int) best_path->pathtype); @@ -1072,6 +1075,96 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path) return plan; } +/* + * create_custom_plan + * + * Returns a custom-scan plan for the base relation scanned by 'best_path' + * with restriction clauses 'scan_clauses' and targetlist 'tlist'. + */ +static Plan * +create_custom_plan(PlannerInfo *root, CustomPath *best_path) +{ + CustomPlan *custom_plan; + RelOptInfo *rel; + List *tlist = NIL; + List *clauses = NIL; + + /* + * Create a custom-plan object delivered from CustomPlan type, + * according to the supplied CustomPath + */ + Assert(best_path->path.pathtype == T_CustomPlan || + best_path->path.pathtype == T_CustomPlanMarkPos); + custom_plan = best_path->methods->CreateCustomPlan(root, best_path); + Assert(nodeTag(custom_plan) == best_path->path.pathtype); + + rel = best_path->path.parent; + if (rel) + { + if (rel->reloptkind == RELOPT_BASEREL) + { + Assert(rel->relid > 0); + custom_plan->scan.scanrelid = rel->relid; + + /* + * For table scans, rather than using the relation targetlist + * (which is only those Vars actually needed by the query), + * we prefer to generate a tlist containing all Vars in order. + * This will allow the executor to optimize away projection of + * the table tuples, if possible. + */ + if (use_physical_tlist(root, rel)) + tlist = build_physical_tlist(root, rel); + } + /* elsewhere, we generate a tlist from the relation targetlist */ + if (tlist == NIL) + tlist = build_path_tlist(root, &best_path->path); + + /* + * Extract the relevant restriction clauses from the parent relation. + * The executor must apply all these restrictions during the scan, + * except for pseudoconstants which we'll take care of below. + */ + clauses = rel->baserestrictinfo; + + /* + * If this is a parameterized scan, we also need to enforce all + * the join clauses available from the outer relation(s). + */ + if (best_path->path.param_info) + clauses = list_concat(list_copy(clauses), + best_path->path.param_info->ppi_clauses); + + /* Sort clauses into best execution order */ + clauses = order_qual_clauses(root, clauses); + + /* + * Replace outer-relation variables with nestloop params. + * Note that any other clauses which is managed by extension + * itself has to be handled in InitCustomPlan() method, as + * built-in code doing. + */ + if (best_path->path.param_info) + clauses = (List *)replace_nestloop_params(root, (Node *)clauses); + } + /* + * Copy cost data from Path to Plan; no need to make custom-plan + * providers do this + */ + copy_path_costsize((Plan *)custom_plan, (Path *)best_path); + + /* + * Let the custom-plan provider perform its final initialization + * of this CustomPlan (to be an inherited type, actually) node + * according to its own necessity. + * Note that custom-plan provider may/can replace (or stack another + * one on) its own custom-plan node on demand, for example, to add + * Result node to handle pseudo constant using create_gating_plan(). + */ + return custom_plan->methods->InitCustomPlan(custom_plan, + root, best_path, + tlist, clauses); +} /***************************************************************************** * @@ -2540,7 +2633,7 @@ create_hashjoin_plan(PlannerInfo *root, * root->curOuterRels are replaced by Params, and entries are added to * root->curOuterParams if not already present. */ -static Node * +Node * replace_nestloop_params(PlannerInfo *root, Node *expr) { /* No setup needed for tree walk, so away we go */ diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 768c5c7..627946e 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -94,7 +94,6 @@ static Plan *set_subqueryscan_references(PlannerInfo *root, SubqueryScan *plan, int rtoffset); static bool trivial_subqueryscan(SubqueryScan *plan); -static Node *fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset); static Node *fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context); static bool fix_scan_expr_walker(Node *node, fix_scan_expr_context *context); static void set_join_references(PlannerInfo *root, Join *join, int rtoffset); @@ -576,6 +575,14 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) } break; + case T_CustomPlan: + { + CustomPlan *cplan = (CustomPlan *) plan; + + cplan->methods->SetCustomPlanRef(root, cplan, rtoffset); + } + break; + case T_NestLoop: case T_MergeJoin: case T_HashJoin: @@ -1126,7 +1133,7 @@ fix_expr_common(PlannerInfo *root, Node *node) * looking up operator opcode info for OpExpr and related nodes, * and adding OIDs from regclass Const nodes into root->glob->relationOids. */ -static Node * +Node * fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset) { fix_scan_expr_context context; diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c index be92049..070634f 100644 --- a/src/backend/optimizer/plan/subselect.c +++ b/src/backend/optimizer/plan/subselect.c @@ -2236,6 +2236,21 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params, context.paramids = bms_add_members(context.paramids, scan_params); break; + case T_CustomPlan: + { + CustomPlan *cplan = (CustomPlan *) plan; + + context.paramids = + cplan->methods->FinalizeCustomPlan(root, + cplan, + context.paramids, + valid_params, + scan_params, + finalize_primnode, + (void *)&context); + } + break; + case T_ModifyTable: { ModifyTable *mtplan = (ModifyTable *) plan; diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index 4e05dcd..1e4150f 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -16,6 +16,10 @@ #include +#include "access/genam.h" +#include "access/heapam.h" +#include "access/htup_details.h" +#include "catalog/pg_custom_plan.h" #include "miscadmin.h" #include "nodes/nodeFuncs.h" #include "optimizer/clauses.h" @@ -25,8 +29,11 @@ #include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" #include "parser/parsetree.h" +#include "utils/inval.h" #include "utils/lsyscache.h" +#include "utils/memutils.h" #include "utils/selfuncs.h" +#include "utils/syscache.h" typedef enum @@ -2067,3 +2074,106 @@ reparameterize_path(PlannerInfo *root, Path *path, } return NULL; } + +/***************************************************************************** + * creation of custom-plan paths + *****************************************************************************/ +static List *custom_scan_callchain = NIL; +static bool custom_plan_callchain_is_ready = false; +static MemoryContext custom_plan_memcxt = NULL; + +static void +invalidate_custom_plan_callchain(Datum arg, int cacheid, uint32 hashvalue) +{ + MemoryContextReset(custom_plan_memcxt); + custom_plan_callchain_is_ready = false; + custom_scan_callchain = NIL; +} + +static void +setup_custom_plan_callchain(void) +{ + Relation rel; + SysScanDesc scan; + HeapTuple tuple; + MemoryContext oldcxt; + + custom_scan_callchain = NIL; + + rel = heap_open(CustomPlanRelationId, AccessShareLock); + + /* full scan on the pg_custom_plan once */ + scan = systable_beginscan(rel, InvalidOid, false, NULL, 0, NULL); + + oldcxt = MemoryContextSwitchTo(custom_plan_memcxt); + while (HeapTupleIsValid(tuple = systable_getnext(scan))) + { + Form_pg_custom_plan custForm + = (Form_pg_custom_plan) GETSTRUCT(tuple); + + if ((custForm->custclass & CUSTOMPLAN_CLASS_SCAN) != 0) + { + custom_scan_callchain = lappend_oid(custom_scan_callchain, + custForm->custprovider); + } + else + elog(LOG, "Bug? custom-plan \"%s\" has unknown class: %c", + NameStr(custForm->custname), custForm->custclass); + } + MemoryContextSwitchTo(oldcxt); + systable_endscan(scan); + + heap_close(rel, AccessShareLock); + + custom_plan_callchain_is_ready = true; +} + +static void +init_custom_plan_callchain(void) +{ + /* memory context to keep callchain for custom-plans */ + custom_plan_memcxt = AllocSetContextCreate(CacheMemoryContext, + "custom plan memory context", + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); + + /* flush cached callchain on catalog updates */ + CacheRegisterSyscacheCallback(CUSTOMPLANOID, + invalidate_custom_plan_callchain, + (Datum) 0); + /* also, initial setting up */ + setup_custom_plan_callchain(); +} + +/* + * call_custom_scan_providers + * + * A callchain on relation scan. custom-plan provider can add alternative + * scan paths delivered from CustomPath class. + */ +void +call_custom_scan_providers(PlannerInfo *root, + RelOptInfo *baserel, + RangeTblEntry *rte) +{ + customScanArg sarg; + ListCell *cell; + + if (!custom_plan_memcxt) + init_custom_plan_callchain(); + else if (!custom_plan_callchain_is_ready) + setup_custom_plan_callchain(); + + Assert(custom_plan_callchain_is_ready); + sarg.custom_class = CUSTOMPLAN_CLASS_SCAN; + sarg.root = root; + sarg.baserel = baserel; + sarg.rte = rte; + + foreach (cell, custom_scan_callchain) + { + (void) OidFunctionCall1(lfirst_oid(cell), + PointerGetDatum(&sarg)); + } +} diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 7b9895d..6881484 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -51,6 +51,7 @@ #include "catalog/index.h" #include "catalog/namespace.h" +#include "catalog/pg_custom_plan.h" #include "catalog/pg_trigger.h" #include "commands/defrem.h" #include "commands/trigger.h" @@ -248,6 +249,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); DropOwnedStmt ReassignOwnedStmt AlterTSConfigurationStmt AlterTSDictionaryStmt CreateMatViewStmt RefreshMatViewStmt + CreateCustomPlanStmt DropCustomPlanStmt %type select_no_parens select_with_parens select_clause simple_select values_clause @@ -500,6 +502,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type opt_existing_window_name %type opt_if_not_exists +%type custom_class +%type custom_option +%type opt_custom_options custom_options + /* * Non-keyword token types. These are hard-wired into the "flex" lexer. * They must be listed first so that their numeric codes do not depend on @@ -535,7 +541,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); CONTENT_P CONTINUE_P CONVERSION_P COPY COST CREATE CROSS CSV CURRENT_P CURRENT_CATALOG CURRENT_DATE CURRENT_ROLE CURRENT_SCHEMA - CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE + CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CUSTOM CYCLE DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DESC @@ -574,8 +580,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); OBJECT_P OF OFF OFFSET OIDS ON ONLY OPERATOR OPTION OPTIONS OR ORDER ORDINALITY OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER - PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POSITION - PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY + PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLAN PLANS POSITION + PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY PROVIDER PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROGRAM QUOTE @@ -585,8 +591,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROW ROWS RULE - SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES - SERIALIZABLE SERVER SESSION SESSION_USER SET SETOF SHARE + SAVEPOINT SCAN SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE + SEQUENCES SERIALIZABLE SERVER SESSION SESSION_USER SET SETOF SHARE SHOW SIMILAR SIMPLE SMALLINT SNAPSHOT SOME STABLE STANDALONE_P START STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBSTRING SYMMETRIC SYSID SYSTEM_P @@ -748,6 +754,7 @@ stmt : | CreateAssertStmt | CreateCastStmt | CreateConversionStmt + | CreateCustomPlanStmt | CreateDomainStmt | CreateExtensionStmt | CreateFdwStmt @@ -778,6 +785,7 @@ stmt : | DoStmt | DropAssertStmt | DropCastStmt + | DropCustomPlanStmt | DropFdwStmt | DropForeignServerStmt | DropGroupStmt @@ -8676,6 +8684,77 @@ CreateConversionStmt: } ; +/**************************************************************************** + * + * QUERY: + * CREATE CUSTOM PLAN name + * + ****************************************************************************/ + +CreateCustomPlanStmt: + CREATE CUSTOM PLAN name FOR custom_class opt_custom_options + { + CreateCustomPlanStmt *n = makeNode(CreateCustomPlanStmt); + n->custom_name = $4; + n->custom_class = $6; + n->custom_options = $7; + $$ = (Node *) n; + } + ; + +custom_class: + SCAN { $$ = CUSTOMPLAN_CLASS_SCAN; } + ; + +custom_option: + PROVIDER handler_name + { + $$ = makeDefElem("provider", (Node *)$2); + } + ; + +custom_options: + custom_option { $$ = list_make1($1); } + | custom_options custom_option { $$ = lappend($1, $2); } + ; + +opt_custom_options: + custom_options { $$ = $1; } + | /* empty */ { $$ = NIL; } + ; + +/**************************************************************************** + * + * QUERY: + * DROP CUSTOM PLAN name + * + ****************************************************************************/ + +DropCustomPlanStmt: + DROP CUSTOM PLAN name opt_drop_behavior + { + DropStmt *n = makeNode(DropStmt); + n->removeType = OBJECT_CUSTOM_PLAN; + n->objects = list_make1(list_make1(makeString($4))); + n->arguments = NIL; + n->missing_ok = false; + n->behavior = $5; + n->concurrent = false; + $$ = (Node *) n; + } + | DROP CUSTOM PLAN IF_P EXISTS name opt_drop_behavior + { + DropStmt *n = makeNode(DropStmt); + n->removeType = OBJECT_CUSTOM_PLAN; + n->objects = list_make1(list_make1(makeString($6))); + n->arguments = NIL; + n->missing_ok = true; + n->behavior = $7; + n->concurrent = false; + $$ = (Node *) n; + } + ; + /***************************************************************************** * * QUERY: @@ -12842,6 +12921,7 @@ unreserved_keyword: | CSV | CURRENT_P | CURSOR + | CUSTOM | CYCLE | DATA_P | DATABASE @@ -12954,6 +13034,7 @@ unreserved_keyword: | PARTITION | PASSING | PASSWORD + | PLAN | PLANS | PRECEDING | PREPARE @@ -12964,6 +13045,7 @@ unreserved_keyword: | PROCEDURAL | PROCEDURE | PROGRAM + | PROVIDER | QUOTE | RANGE | READ @@ -12989,6 +13071,7 @@ unreserved_keyword: | ROWS | RULE | SAVEPOINT + | SCAN | SCHEMA | SCROLL | SEARCH diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 3423898..3aa7d5f 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -202,6 +202,7 @@ check_xact_readonly(Node *parsetree) case T_AlterTableSpaceOptionsStmt: case T_AlterTableSpaceMoveStmt: case T_CreateForeignTableStmt: + case T_CreateCustomPlanStmt: case T_SecLabelStmt: PreventCommandIfReadOnly(CreateCommandTag(parsetree)); break; @@ -683,6 +684,10 @@ standard_ProcessUtility(Node *parsetree, AlterEventTrigger((AlterEventTrigStmt *) parsetree); break; + case T_CreateCustomPlanStmt: + DefineCustomPlan((CreateCustomPlanStmt *) parsetree); + break; + /* * ******************************** ROLE statements **** */ @@ -1940,6 +1945,9 @@ CreateCommandTag(Node *parsetree) case OBJECT_OPFAMILY: tag = "DROP OPERATOR FAMILY"; break; + case OBJECT_CUSTOM_PLAN: + tag = "DROP CUSTOM PLAN"; + break; default: tag = "???"; } @@ -2207,6 +2215,10 @@ CreateCommandTag(Node *parsetree) tag = "ALTER EVENT TRIGGER"; break; + case T_CreateCustomPlanStmt: + tag = "CREATE CUSTOM PLAN"; + break; + case T_CreatePLangStmt: tag = "CREATE LANGUAGE"; break; @@ -2830,6 +2842,10 @@ GetCommandLogLevel(Node *parsetree) lev = LOGSTMT_DDL; break; + case T_CreateCustomPlanStmt: + lev = LOGSTMT_DDL; + break; + /* already-planned queries */ case T_PlannedStmt: { diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index a30d8fe..f4c5d92 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -5387,6 +5387,24 @@ get_utility_query_def(Query *query, deparse_context *context) } } +/* + * GetSpecialCustomVar + * + * This routine provides a way to resolve where the supplied varnode + * actually references, using GetSpecialCustomVar method, in case when + * custom-plan provider replaced a varno in expression tree by special + * varno. + */ +static Node * +GetSpecialCustomVar(PlanState *ps, Var *varnode) +{ + CustomPlanState *cps = (CustomPlanState *) ps; + + Assert(IsA(ps, CustomPlanState)); + Assert(IS_SPECIAL_VARNO(varnode->varno)); + + return (Node *)cps->methods->GetSpecialCustomVar(cps, varnode); +} /* * Display a Var appropriately. @@ -5416,6 +5434,7 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context) int netlevelsup; deparse_namespace *dpns; deparse_columns *colinfo; + Node *expr; char *refname; char *attname; @@ -5440,6 +5459,21 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context) colinfo = deparse_columns_fetch(var->varno, dpns); attnum = var->varattno; } + else if (IS_SPECIAL_VARNO(var->varno) && + IsA(dpns->planstate, CustomPlanState) && + (expr = GetSpecialCustomVar(dpns->planstate, var)) != NULL) + { + /* + * Force parentheses because our caller probably assumed a Var is a + * simple expression. + */ + if (!IsA(expr, Var)) + appendStringInfoChar(buf, '('); + get_rule_expr((Node *) expr, context, true); + if (!IsA(expr, Var)) + appendStringInfoChar(buf, ')'); + return NULL; + } else if (var->varno == OUTER_VAR && dpns->outer_tlist) { TargetEntry *tle; @@ -5728,6 +5762,26 @@ get_name_for_var_field(Var *var, int fieldno, rte = rt_fetch(var->varno, dpns->rtable); attnum = var->varattno; } + else if (IS_SPECIAL_VARNO(var->varno) && + IsA(dpns->planstate, CustomPlanState) && + (expr = GetSpecialCustomVar(dpns->planstate, var)) != NULL) + { + StringInfo saved = context->buf; + StringInfoData temp; + + initStringInfo(&temp); + context->buf = &temp; + + if (!IsA(expr, Var)) + appendStringInfoChar(context->buf, '('); + get_rule_expr((Node *) expr, context, true); + if (!IsA(expr, Var)) + appendStringInfoChar(context->buf, ')'); + + context->buf = saved; + + return temp.data; + } else if (var->varno == OUTER_VAR && dpns->outer_tlist) { TargetEntry *tle; diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c index 94d951c..20d7767 100644 --- a/src/backend/utils/cache/syscache.c +++ b/src/backend/utils/cache/syscache.c @@ -32,6 +32,7 @@ #include "catalog/pg_collation.h" #include "catalog/pg_constraint.h" #include "catalog/pg_conversion.h" +#include "catalog/pg_custom_plan.h" #include "catalog/pg_database.h" #include "catalog/pg_db_role_setting.h" #include "catalog/pg_default_acl.h" @@ -345,6 +346,28 @@ static const struct cachedesc cacheinfo[] = { }, 8 }, + {CustomPlanRelationId, /* CUSTOMPLANOID */ + CustomPlanOidIndexId, + 1, + { + ObjectIdAttributeNumber, + 0, + 0, + 0 + }, + 32 + }, + {CustomPlanRelationId, /* CUSTOMPLANNAME */ + CustomPlanNameIndexId, + 1, + { + Anum_pg_custom_plan_custname, + 0, + 0, + 0, + }, + 32 + }, {DatabaseRelationId, /* DATABASEOID */ DatabaseOidIndexId, 1, diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h index 8ed2592..56f5371 100644 --- a/src/include/catalog/dependency.h +++ b/src/include/catalog/dependency.h @@ -147,6 +147,7 @@ typedef enum ObjectClass OCLASS_DEFACL, /* pg_default_acl */ OCLASS_EXTENSION, /* pg_extension */ OCLASS_EVENT_TRIGGER, /* pg_event_trigger */ + OCLASS_CUSTOM_PLAN, /* pg_custom_plan */ MAX_OCLASS /* MUST BE LAST */ } ObjectClass; diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h index 0515b75..a5e6cea 100644 --- a/src/include/catalog/indexing.h +++ b/src/include/catalog/indexing.h @@ -313,6 +313,12 @@ DECLARE_UNIQUE_INDEX(pg_extension_name_index, 3081, on pg_extension using btree( DECLARE_UNIQUE_INDEX(pg_range_rngtypid_index, 3542, on pg_range using btree(rngtypid oid_ops)); #define RangeTypidIndexId 3542 +DECLARE_UNIQUE_INDEX(pg_custom_plan_oid_index, 3563, on pg_custom_plan using btree(oid oid_ops)); +#define CustomPlanOidIndexId 3563 + +DECLARE_UNIQUE_INDEX(pg_custom_plan_name_index, 3564, on pg_custom_plan using btree(custname name_ops)); +#define CustomPlanNameIndexId 3564 + /* last step of initialization script: build the indexes declared above */ BUILD_INDICES diff --git a/src/include/catalog/pg_custom_plan.h b/src/include/catalog/pg_custom_plan.h new file mode 100644 index 0000000..5b31a72 --- /dev/null +++ b/src/include/catalog/pg_custom_plan.h @@ -0,0 +1,49 @@ +/* ------------------------------------------------------------------------- + * + * pg_custom_plan.h + * definition of the system "custom plan" relation (pg_custom_plan) + * + * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * ------------------------------------------------------------------------- + */ +#ifndef PG_CUSTOM_PLAN_H +#define PG_CUSTOM_PLAN_H + +#include "catalog/genbki.h" + +/* ---------------- + * pg_custom_plan definition. cpp turns this into + * typedef struct FormData_pg_custom_plan + * ---------------- + */ +#define CustomPlanRelationId 3562 + +CATALOG(pg_custom_plan,3562) +{ + NameData custname; /* name of custom-plan provider */ + char custclass; /* class of custom-plan */ + Oid custprovider; /* function of custom-plan provider */ +} FormData_pg_custom_plan; + +/* ---------------- + * Form_pg_custom_plan corresponds to a pointer to a tuple + * with the format of pg_custom_plan relation. + * ---------------- + */ +typedef FormData_pg_custom_plan *Form_pg_custom_plan; + +/* ---------------- + * compiler constants for pg_custom_plan + * ---------------- + */ +#define Natts_pg_custom_plan 3 +#define Anum_pg_custom_plan_custname 1 +#define Anum_pg_custom_plan_custclass 2 +#define Anum_pg_custom_plan_custprovider 3 + +/* definition of custclass */ +#define CUSTOMPLAN_CLASS_SCAN 's' + +#endif /* PG_CUSTOM_PLAN_H */ diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h index 87ee4eb..942c22a 100644 --- a/src/include/catalog/pg_operator.h +++ b/src/include/catalog/pg_operator.h @@ -166,10 +166,13 @@ DESCR("less than"); #define TIDLessOperator 2799 DATA(insert OID = 2800 ( ">" PGNSP PGUID b f f 27 27 16 2799 2801 tidgt scalargtsel scalargtjoinsel )); DESCR("greater than"); +#define TIDGreaterOperator 2800 DATA(insert OID = 2801 ( "<=" PGNSP PGUID b f f 27 27 16 2802 2800 tidle scalarltsel scalarltjoinsel )); DESCR("less than or equal"); +#define TIDLessEqualOperator 2801 DATA(insert OID = 2802 ( ">=" PGNSP PGUID b f f 27 27 16 2801 2799 tidge scalargtsel scalargtjoinsel )); DESCR("greater than or equal"); +#define TIDGreaterEqualOperator 2802 DATA(insert OID = 410 ( "=" PGNSP PGUID b t t 20 20 16 410 411 int8eq eqsel eqjoinsel )); DESCR("equal"); diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index 5ec9374..ee36614 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -129,6 +129,11 @@ extern Datum transformGenericOptions(Oid catalogId, List *options, Oid fdwvalidator); +/* commands/custom_plan.c */ +extern Oid get_custom_plan_oid(const char *custom_name, bool missing_ok); +extern void RemoveCustomPlanById(Oid cust_oid); +extern Oid DefineCustomPlan(CreateCustomPlanStmt *stmt); + /* support routines in commands/define.c */ extern char *defGetString(DefElem *def); diff --git a/src/include/executor/nodeCustom.h b/src/include/executor/nodeCustom.h new file mode 100644 index 0000000..50a4edb --- /dev/null +++ b/src/include/executor/nodeCustom.h @@ -0,0 +1,30 @@ +/* ------------------------------------------------------------------------ + * + * nodeCustom.h + * + * prototypes for CustomScan nodes + * + * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * ------------------------------------------------------------------------ + */ +#ifndef NODECUSTOM_H +#define NODECUSTOM_H +#include "nodes/plannodes.h" +#include "nodes/execnodes.h" + +/* + * General executor code + */ +extern CustomPlanState *ExecInitCustomPlan(CustomPlan *cplan, + EState *estate, int eflags); +extern TupleTableSlot *ExecCustomPlan(CustomPlanState *node); +extern Node *MultiExecCustomPlan(CustomPlanState *node); +extern void ExecEndCustomPlan(CustomPlanState *node); + +extern void ExecReScanCustomPlan(CustomPlanState *node); +extern void ExecCustomMarkPos(CustomPlanState *node); +extern void ExecCustomRestrPos(CustomPlanState *node); + +#endif /* NODECUSTOM_H */ diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 0ab2a13..06710a9 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -1501,6 +1501,47 @@ typedef struct ForeignScanState void *fdw_state; /* foreign-data wrapper can keep state here */ } ForeignScanState; +/* ---------------- + * CustomPlanState information + * + * CustomPlan nodes are used to execute custom code within executor. + * ---------------- + */ +struct CustomExecMethods; +struct ExplainState; /* to avoid to include explain.h here */ + +typedef struct CustomPlanState +{ + ScanState ss; + const struct CustomExecMethods *methods; +} CustomPlanState; + +typedef struct CustomExecMethods +{ + const char *CustomName; + + /* EXECUTOR methods */ + void (*BeginCustomPlan)(CustomPlanState *node, + EState *estate, + int eflags); + TupleTableSlot *(*ExecCustomPlan)(CustomPlanState *node); + Node *(*MultiExecCustomPlan)(CustomPlanState *node); + void (*EndCustomPlan)(CustomPlanState *node); + void (*ReScanCustomPlan)(CustomPlanState *node); + void (*MarkPosCustomPlan)(CustomPlanState *node); + void (*RestrPosCustomPlan)(CustomPlanState *node); + + /* EXPLAIN support */ + void (*ExplainCustomPlanTargetRel)(CustomPlanState *node, + struct ExplainState *es); + void (*ExplainCustomPlan)(CustomPlanState *node, + List *ancestors, + struct ExplainState *es); + void (*ExplainCustomPreScanNode)(CustomPlanState *node, + Bitmapset **rels_used); + Node *(*GetSpecialCustomVar)(CustomPlanState *node, Var *varnode); +} CustomExecMethods; + /* ---------------------------------------------------------------- * Join State Information * ---------------------------------------------------------------- diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index bc58e16..99e9b2d 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -62,6 +62,8 @@ typedef enum NodeTag T_CteScan, T_WorkTableScan, T_ForeignScan, + T_CustomPlan, + T_CustomPlanMarkPos, T_Join, T_NestLoop, T_MergeJoin, @@ -107,6 +109,7 @@ typedef enum NodeTag T_CteScanState, T_WorkTableScanState, T_ForeignScanState, + T_CustomPlanState, T_JoinState, T_NestLoopState, T_MergeJoinState, @@ -224,6 +227,7 @@ typedef enum NodeTag T_HashPath, T_TidPath, T_ForeignPath, + T_CustomPath, T_AppendPath, T_MergeAppendPath, T_ResultPath, @@ -365,6 +369,7 @@ typedef enum NodeTag T_RefreshMatViewStmt, T_ReplicaIdentityStmt, T_AlterSystemStmt, + T_CreateCustomPlanStmt, /* * TAGS FOR PARSE TREE NODES (parsenodes.h) diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 7e560a1..d30c7d5 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1195,6 +1195,7 @@ typedef enum ObjectType OBJECT_CONSTRAINT, OBJECT_COLLATION, OBJECT_CONVERSION, + OBJECT_CUSTOM_PLAN, OBJECT_DATABASE, OBJECT_DOMAIN, OBJECT_EVENT_TRIGGER, @@ -2036,6 +2037,18 @@ typedef struct AlterOpFamilyStmt } AlterOpFamilyStmt; /* ---------------------- + * Create Custom Plan Statement + * ---------------------- + */ +typedef struct CreateCustomPlanStmt +{ + NodeTag type; + char *custom_name; /* name of custom-plan provider */ + char custom_class; /* class of custom-plan provides */ + List *custom_options; /* generic options for provider */ +} CreateCustomPlanStmt; + +/* ---------------------- * Drop Table|Sequence|View|Index|Type|Domain|Conversion|Schema Statement * ---------------------- */ diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 3b9c683..0b01580 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -15,6 +15,7 @@ #define PLANNODES_H #include "access/sdir.h" +#include "lib/stringinfo.h" #include "nodes/bitmapset.h" #include "nodes/primnodes.h" @@ -479,6 +480,45 @@ typedef struct ForeignScan bool fsSystemCol; /* true if any "system column" is needed */ } ForeignScan; +/* ---------------- + * CustomPlan node + * ---------------- + */ +struct CustomPlanMethods; +struct CustomPlanState; /* to avoid to include nodes/execnodes.h here */ +struct CustomPath; /* to avoid to include nodes/relation.h here */ +struct PlannerInfo; /* to avoid to include nodes/relation.h here */ + +typedef struct CustomPlan +{ + Scan scan; + const struct CustomPlanMethods *methods; +} CustomPlan; + +typedef struct CustomPlanMethods +{ + const char *CustomName; + Plan *(*InitCustomPlan)(CustomPlan *custom_plan, + struct PlannerInfo *root, + struct CustomPath *best_path, + List *tlist, + List *clauses); + void (*SetCustomPlanRef)(struct PlannerInfo *root, + CustomPlan *custom_plan, + int rtoffset); + bool (*SupportBackwardScan)(CustomPlan *custom_plan); + Bitmapset *(*FinalizeCustomPlan)(struct PlannerInfo *root, + CustomPlan *custom_plan, + Bitmapset *paramids, + Bitmapset *valid_params, + Bitmapset *scan_params, + bool (*finalize_primnode)(), + void *finalize_context); + struct CustomPlanState *(*CreateCustomPlanState)(CustomPlan *custom_plan); + void (*TextOutCustomPlan)(StringInfo str, + const CustomPlan *node); + CustomPlan *(*CopyCustomPlan)(const CustomPlan *from); +} CustomPlanMethods; /* * ========== diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 300136e..b0e7c36 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -15,6 +15,7 @@ #define RELATION_H #include "access/sdir.h" +#include "lib/stringinfo.h" #include "nodes/params.h" #include "nodes/parsenodes.h" #include "storage/block.h" @@ -878,6 +879,33 @@ typedef struct ForeignPath } ForeignPath; /* + * CustomPath represents a scan using custom logic + * + * Extension (that performs as custom-plan provider) can adds an alternative + * path using its custom type being delivered from CustomPath. + * They can store their private data on the extra fields of their custom + * object. A set of common methods are represented as function pointers in + * CustomPathMethods structure; extension has to set up then correctly. + */ +struct CustomPathMethods; +struct Plan; /* to avoid to include plannode.h here */ +struct CustomPlan; /* to avoid to include plannode.h here */ + +typedef struct CustomPath +{ + Path path; + const struct CustomPathMethods *methods; +} CustomPath; + +typedef struct CustomPathMethods +{ + const char *CustomName; + struct CustomPlan *(*CreateCustomPlan)(PlannerInfo *root, + CustomPath *best_path); + void (*TextOutCustomPath)(StringInfo str, const CustomPath *node); +} CustomPathMethods; + +/* * AppendPath represents an Append plan, ie, successive execution of * several member plans. * diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index a0bcc82..df4d6e8 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -129,6 +129,20 @@ extern Path *reparameterize_path(PlannerInfo *root, Path *path, double loop_count); /* + * interface towards custom-plan provider functions + */ +typedef struct { + uint32 custom_class; + PlannerInfo *root; + RelOptInfo *baserel; + RangeTblEntry *rte; +} customScanArg; + +extern void call_custom_scan_providers(PlannerInfo *root, + RelOptInfo *baserel, + RangeTblEntry *rte); + +/* * prototypes for relnode.c */ extern void setup_simple_rel_arrays(PlannerInfo *root); diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h index 8bdb7db..76e3c86 100644 --- a/src/include/optimizer/planmain.h +++ b/src/include/optimizer/planmain.h @@ -86,6 +86,7 @@ extern ModifyTable *make_modifytable(PlannerInfo *root, List *withCheckOptionLists, List *returningLists, List *rowMarks, int epqParam); extern bool is_projection_capable_plan(Plan *plan); +extern Node *replace_nestloop_params(PlannerInfo *root, Node *expr); /* * prototypes for plan/initsplan.c @@ -128,6 +129,7 @@ extern List *remove_useless_joins(PlannerInfo *root, List *joinlist); */ extern Plan *set_plan_references(PlannerInfo *root, Plan *plan); extern void fix_opfuncids(Node *node); +extern Node *fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset); extern void set_opfuncid(OpExpr *opexpr); extern void set_sa_opfuncid(ScalarArrayOpExpr *opexpr); extern void record_plan_function_dependency(PlannerInfo *root, Oid funcid); diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 61fae22..9851fe9 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -107,6 +107,7 @@ PG_KEYWORD("current_time", CURRENT_TIME, RESERVED_KEYWORD) PG_KEYWORD("current_timestamp", CURRENT_TIMESTAMP, RESERVED_KEYWORD) PG_KEYWORD("current_user", CURRENT_USER, RESERVED_KEYWORD) PG_KEYWORD("cursor", CURSOR, UNRESERVED_KEYWORD) +PG_KEYWORD("custom", CUSTOM, UNRESERVED_KEYWORD) PG_KEYWORD("cycle", CYCLE, UNRESERVED_KEYWORD) PG_KEYWORD("data", DATA_P, UNRESERVED_KEYWORD) PG_KEYWORD("database", DATABASE, UNRESERVED_KEYWORD) @@ -283,6 +284,7 @@ PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD) PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD) PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD) PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD) +PG_KEYWORD("plan", PLAN, UNRESERVED_KEYWORD) PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD) PG_KEYWORD("position", POSITION, COL_NAME_KEYWORD) PG_KEYWORD("preceding", PRECEDING, UNRESERVED_KEYWORD) @@ -296,6 +298,7 @@ PG_KEYWORD("privileges", PRIVILEGES, UNRESERVED_KEYWORD) PG_KEYWORD("procedural", PROCEDURAL, UNRESERVED_KEYWORD) PG_KEYWORD("procedure", PROCEDURE, UNRESERVED_KEYWORD) PG_KEYWORD("program", PROGRAM, UNRESERVED_KEYWORD) +PG_KEYWORD("provider", PROVIDER, UNRESERVED_KEYWORD) PG_KEYWORD("quote", QUOTE, UNRESERVED_KEYWORD) PG_KEYWORD("range", RANGE, UNRESERVED_KEYWORD) PG_KEYWORD("read", READ, UNRESERVED_KEYWORD) @@ -326,6 +329,7 @@ PG_KEYWORD("row", ROW, COL_NAME_KEYWORD) PG_KEYWORD("rows", ROWS, UNRESERVED_KEYWORD) PG_KEYWORD("rule", RULE, UNRESERVED_KEYWORD) PG_KEYWORD("savepoint", SAVEPOINT, UNRESERVED_KEYWORD) +PG_KEYWORD("scan", SCAN, UNRESERVED_KEYWORD) PG_KEYWORD("schema", SCHEMA, UNRESERVED_KEYWORD) PG_KEYWORD("scroll", SCROLL, UNRESERVED_KEYWORD) PG_KEYWORD("search", SEARCH, UNRESERVED_KEYWORD) diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h index f97229f..7272eec 100644 --- a/src/include/utils/syscache.h +++ b/src/include/utils/syscache.h @@ -52,6 +52,8 @@ enum SysCacheIdentifier CONNAMENSP, CONSTROID, CONVOID, + CUSTOMPLANOID, + CUSTOMPLANNAME, DATABASEOID, DEFACLROLENSPOBJ, ENUMOID, diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out index 111d24c..e3cad45 100644 --- a/src/test/regress/expected/sanity_check.out +++ b/src/test/regress/expected/sanity_check.out @@ -97,6 +97,7 @@ pg_class|t pg_collation|t pg_constraint|t pg_conversion|t +pg_custom_plan|t pg_database|t pg_db_role_setting|t pg_default_acl|t