doc/src/sgml/custom-scan.sgml | 295 ++++++++++++++++++++++++++++++++ doc/src/sgml/filelist.sgml | 1 + doc/src/sgml/postgres.sgml | 1 + src/backend/commands/explain.c | 99 +++++++++++ src/backend/executor/Makefile | 2 +- src/backend/executor/execAmi.c | 34 +++- src/backend/executor/execProcnode.c | 14 ++ src/backend/executor/execQual.c | 10 +- src/backend/executor/execUtils.c | 4 +- src/backend/executor/nodeCustom.c | 252 +++++++++++++++++++++++++++ src/backend/nodes/copyfuncs.c | 30 ++++ src/backend/nodes/outfuncs.c | 19 ++ src/backend/nodes/print.c | 4 + src/backend/optimizer/path/allpaths.c | 23 +++ src/backend/optimizer/path/costsize.c | 2 +- src/backend/optimizer/path/joinpath.c | 18 ++ src/backend/optimizer/plan/createplan.c | 104 +++++++++++ src/backend/optimizer/plan/setrefs.c | 25 +++ src/backend/optimizer/plan/subselect.c | 10 ++ src/backend/optimizer/util/pathnode.c | 40 +++++ src/backend/utils/adt/ruleutils.c | 44 ++++- src/include/executor/executor.h | 3 +- src/include/executor/nodeCustom.h | 94 ++++++++++ src/include/nodes/execnodes.h | 17 ++ src/include/nodes/nodes.h | 3 + src/include/nodes/plannodes.h | 16 ++ src/include/nodes/primnodes.h | 1 + src/include/nodes/relation.h | 16 ++ src/include/optimizer/pathnode.h | 10 ++ src/include/optimizer/paths.h | 25 +++ 30 files changed, 1201 insertions(+), 15 deletions(-) diff --git a/doc/src/sgml/custom-scan.sgml b/doc/src/sgml/custom-scan.sgml new file mode 100644 index 0000000..b57d82f --- /dev/null +++ b/doc/src/sgml/custom-scan.sgml @@ -0,0 +1,295 @@ + + + + Writing A Custom Scan Provider + + + custom scan + handler for + + + The custom-scan API enables an extension to provide alternative ways to scan + or join relations leveraging the cost based optimizer. The API consists of a + set of callbacks, with a unique names, to be invoked during query planning + and execution. A custom-scan provider should implement these callback + functions according to the expectation of the API. + + + Overall, there are four major tasks that a custom-scan provider should + implement. The first task is the registration of custom-scan provider itself. + Usually, this needs to be done once at the _PG_init() + entrypoint when the module is loading. The remaing three tasks are all done + when a query is planning and executing. The second task is the submission of + candidate paths to either scan or join relations with an adequate cost for + the core planner. Then, the planner will choose the cheapest path from all of + the candidates. If the custom path survived, the planner starts the third + task; construction of a CustomScan plan node, located + within the query plan tree instead of the built-in plan node. The last task + is the execution of its implementation in answer to invocations by the core + executor. + + + Some of contrib modules utilize the custom-scan API. They may provide a good + example for new development. + + + + + + This custom scan in this module enables a scan to skip earlier pages or + terminate prior to end of the relation, if the inequality operator on the + ctid system column can narrow down the scope to be + scanned, instead of a sequential scan which reads a relation from the + head to the end. + + + + + + + + This custom scan in this module replaces a local join of foreign tables + managed by postgres_fdw with a scan that fetches + remotely joined relations. It demostrates the way to implement a custom + scan node that performs join nodes. + + + + + + + Currently, only scan and join are fully supported with integrated cost + based query optimization using the custom scan API. You might be able to + implement other stuff, like sort or aggregation, with manipulation of the + planned tree, however, the extension has to be responsible to handle this + replacement correctly. There is no support in the core. + + + + Custom Scan API Functions and Callbacks + + Registration of custom scan provider + + The first task for a custom scan provider is the registration of a set of + callbacks with a unique names. Usually, this is done once upon module + loading in the _PG_init() entrypoint. + +void +register_custom_provider(const CustomProvider *provider); + + Its argument, CustomProvider structure, contains + a name and a set of callback function pointers but some of them are + optional. + Once registered, it is copied on the internal table, so the caller + does not need to keep this structure any more. + + + + + Submission of custom paths + + The query planner finds the best way to scan or join relations from various + potential paths using a combination of scan algorithms and target + relations. Prior to this selection, we list all of the potential paths + towards a target relation (if it is a base relation) or a pair of relations + (if it is a join). The add_scan_path_hook and + add_join_path_hook allow extensions to add alternative scan + paths in addition to built-in paths. + If custom-scan provider can submit a potential scan path towards the + supplied relation, it shall construct a CustomPath object + with appropriate parameters. + +typedef struct CustomPath +{ + Path path; + const char *custom_name; /* name of custom scan provider */ + int custom_flags; /* CUSTOM__* flags in nodeCustom.h */ + List *custom_private; /* can be used for private data */ +} CustomPath; + + Its path is a common field for all the path nodes to store + a cost estimation. In addition, custom_name is the name of + the registered custom scan provider, custom_flags is a set of + flags below, and custom_private can be used to store private + data of the custom scan provider. + + + + + CUSTOM__SUPPORT_MARK_RESTORE + + + It informs the query planner this custom scan node supports + ExecMarkPosCustomScan and + ExecRestorePosCustomScan methods. + Also, the custom scan provider has to be responsible to mark and + restore a particular position. + + + + + CUSTOM__SUPPORT_BACKWARD_SCAN + + + It informs the query planner this custom scan node supports + backward scans. + Also, custom scan provider has to be responsible to scan with + backward direction. + + + + + + + + + Construction of custom plan node + + Once CustomPath was choosen by the query planner, + it calls back to its associated to the custom scan provider to complete + setting up the CustomScan plan node according to the + path information. + +void +InitCustomScanPlan(PlannerInfo *root, + CustomScan *cscan_plan, + CustomPath *cscan_path, + List *tlist, + List *scan_clauses); + + The query planner does basic initialization on the cscan_plan + being allocated, then the custom scan provider can apply final + initialization. cscan_path is the path node that was + constructed on the previous stage then was choosen. + tlist is a list of TargetEntry to be assigned + on the Plan portion in the cscan_plan. + Also, scan_clauses is a list of RestrictInfo to + be checked during a relation scan. Its expression portion will also be + assigned on the Plan portion, but can be eliminated from + this list if custom scan provider can handle these checks by itself. + + + It often needs to adjust varno of Var node that + references a particular scan node, after construction of the plan node. + For example, Var node in the target list of the join node originally + references a particular relation underlying a join, however, it has to + be adjusted to either inner or outer reference. + +void +SetPlanRefCustomScan(PlannerInfo *root, + CustomScan *cscan_plan, + int rtoffset); + + This callback is optional if the custom scan node is a vanilla relation + scan because there is nothing special to do. Elsewhere, it needs to + be handled by the custom scan provider in case when a custom scan replaced + a join with two or more relations for example. + + + + + Execution of custom scan node + + The query executor also launches the associated callbacks to begin, execute + and end the custom scan according to the executor's manner. + + + +void +BeginCustomScan(CustomScanState *csstate, int eflags); + + It begins execution of the custom scan on starting up executor. + It allows the custom scan provider to do any initialization job around this + plan, however, it is not a good idea to launch the actual scanning jobs. + (It shall be done on the first invocation of ExecCustomScan + instead.) + The custom_state field of CustomScanState is + intended to save the private state being managed by the custom scan + provider. Also, eflags has flag bits of the executor's + operating mode for this plan node. Note that the custom scan provider + should not perform anything visible externally if + EXEC_FLAG_EXPLAIN_ONLY would be given, + + + + +TupleTableSlot * +ExecCustomScan(CustomScanState *csstate); + + It fetches one tuple from the underlying relation or relations, if joining, + according to the custom logic. Unlike IterateForeignScan + method in foreign table, it is also responsible to check whether the next + tuple matches the qualifier of this scan, or not. + The usual way to implement this method is the callback performs just an + entrypoint of ExecQual with its own access method. + + + + +Node * +MultiExecCustomScan(CustomScanState *csstate); + + It fetches multiple tuples from the underlying relation or relations, if + joining, according to the custom logic. Pay attention the data format (and + the way to return also) since it depends on the type of upper node. + + + + +void +EndCustomScan(CustomScanState *csstate); + + It ends the scan and releases resources privately allocated. + It is usually not important to release memory in per-execution memory + context. So, all this callback should be responsible is its own + resources regardless from the framework. + + + + + Miscellaneous jobs + + +void +ReScanCustomScan(CustomScanState *csstate); + + It restarts the current scan from the beginning. + Note that parameters of the scan depends on may change values, + so rewinded scan does not need to return exactly identical tuples. + + + +void +MarkPosCustomScan(CustomScanState *csstate); + + It saves the current position of the custom scan on somewhere private + state. + Note that it is optional to implement, only when + CUSTOM__SUPPORT_MARK_RESTORE is set. + + + +void +RestorePosCustom(CustomScanState *csstate); + + It rewinds the current position of the custom scan to the position + where MarkPosCustomScan was saved before. + Note that it is optional to implement, only when + CUSTOM__SUPPORT_MARK_RESTORE is set. + + + +void +ExplainCustomScan(CustomScanState *csstate, + ExplainState *es); + + It prints additional EXPLAIN output for a custom scan plan. + This callback is expected to call ExplainPropertyText to + make additional field of EXPLAIN output. + The flag fields in ExplainState indicates what shall be + printed, and the state of the CustomScanState will provide + run-time statistics in the EXPLAIN ANALYZE case. + + + + diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index d1b7dc6..1e96829 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -91,6 +91,7 @@ + diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml index b47bf52..ed76d33 100644 --- a/doc/src/sgml/postgres.sgml +++ b/doc/src/sgml/postgres.sgml @@ -241,6 +241,7 @@ &nls; &plhandler; &fdwhandler; + &custom-scan; &geqo; &indexam; &gist; diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index bd5428d..0532197 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -19,6 +19,7 @@ #include "commands/defrem.h" #include "commands/prepare.h" #include "executor/hashjoin.h" +#include "executor/nodeCustom.h" #include "foreign/fdwapi.h" #include "optimizer/clauses.h" #include "parser/parsetree.h" @@ -84,6 +85,7 @@ static void show_hash_info(HashState *hashstate, ExplainState *es); static void show_instrumentation_count(const char *qlabel, int which, PlanState *planstate, ExplainState *es); static void show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es); +static void show_customscan_info(CustomScanState *cstate, ExplainState *es); static const char *explain_get_index_name(Oid indexId); static void ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir, ExplainState *es); @@ -683,6 +685,11 @@ ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used) *rels_used = bms_add_member(*rels_used, ((Scan *) plan)->scanrelid); break; + case T_CustomScan: + if (((Scan *) plan)->scanrelid > 0) + *rels_used = bms_add_member(*rels_used, + ((Scan *) plan)->scanrelid); + break; case T_ModifyTable: /* cf ExplainModifyTarget */ *rels_used = bms_add_member(*rels_used, @@ -809,6 +816,8 @@ 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; + char namebuf[NAMEDATALEN + 32]; int save_indent = es->indent; bool haschildren; @@ -897,6 +906,13 @@ ExplainNode(PlanState *planstate, List *ancestors, case T_ForeignScan: pname = sname = "Foreign Scan"; break; + case T_CustomScan: + snprintf(namebuf, sizeof(namebuf), "Custom Scan (%s)", + ((CustomScan *) plan)->custom_name); + pname = pstrdup(namebuf); + sname = "Custom Scan"; + custom_name = ((CustomScan *) plan)->custom_name; + break; case T_Material: pname = sname = "Materialize"; break; @@ -998,6 +1014,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 Provider", custom_name, es); } switch (nodeTag(plan)) @@ -1013,6 +1031,10 @@ ExplainNode(PlanState *planstate, List *ancestors, case T_ForeignScan: ExplainScanTarget((Scan *) plan, es); break; + case T_CustomScan: + if (((Scan *) plan)->scanrelid > 0) + ExplainScanTarget((Scan *) plan, es); + break; case T_IndexScan: { IndexScan *indexscan = (IndexScan *) plan; @@ -1303,6 +1325,29 @@ ExplainNode(PlanState *planstate, List *ancestors, planstate, es); show_foreignscan_info((ForeignScanState *) planstate, es); break; + case T_CustomScan: + if (((CustomScan *)plan)->functions != NIL && es->verbose) + { + List *fexprs = NIL; + ListCell *lc; + + foreach(lc, ((CustomScan *) plan)->functions) + { + RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc); + + fexprs = lappend(fexprs, rtfunc->funcexpr); + } + /* We rely on show_expression to insert commas as needed */ + show_expression((Node *) fexprs, + "Function Call", planstate, ancestors, + es->verbose, es); + } + show_scan_qual(plan->qual, "Filter", planstate, ancestors, es); + if (plan->qual) + show_instrumentation_count("Rows Removed by Filter", 1, + planstate, es); + show_customscan_info((CustomScanState *) planstate, es); + break; case T_NestLoop: show_upper_qual(((NestLoop *) plan)->join.joinqual, "Join Filter", planstate, ancestors, es); @@ -1870,6 +1915,19 @@ show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es) } /* + * Show extra information for a CustomScan node. + */ +static void +show_customscan_info(CustomScanState *cstate, ExplainState *es) +{ + CustomProvider *provider = cstate->custom_provider; + + /* Let custom scan provider emit whatever fields it wants */ + if (provider->ExplainCustomScan != NULL) + provider->ExplainCustomScan(cstate, es); +} + +/* * Fetch the name of an index in an EXPLAIN * * We allow plugins to get control here so that plans involving hypothetical @@ -2042,6 +2100,47 @@ ExplainTargetRel(Plan *plan, Index rti, ExplainState *es) objectname = rte->ctename; objecttag = "CTE Name"; break; + case T_CustomScan: + if (rte->rtekind == RTE_RELATION) + { + objectname = get_rel_name(rte->relid); + if (es->verbose) + namespace = + get_namespace_name(get_rel_namespace(rte->relid)); + objecttag = "Relation Name"; + } + else if (rte->rtekind == RTE_JOIN) + { + objectname = rte->eref->aliasname; + objecttag = "Join Alias"; + } + else if (rte->rtekind == RTE_FUNCTION) + { + List *functions = ((CustomScan *) plan)->functions; + + if (functions && list_length(functions) == 1) + { + RangeTblFunction *rtfunc = linitial(functions); + + if (IsA(rtfunc->funcexpr, FuncExpr)) + { + FuncExpr *funcexpr = (FuncExpr *) rtfunc->funcexpr; + Oid funcid = funcexpr->funcid; + + objectname = get_func_name(funcid); + if (es->verbose) + namespace = + get_namespace_name(get_func_namespace(funcid)); + } + } + objecttag = "Function Name"; + } + else if (rte->rtekind == RTE_CTE) + { + objectname = rte->ctename; + objecttag = "CTE Name"; + } + break; default: break; } diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile index 6081b56..4dece5a 100644 --- a/src/backend/executor/Makefile +++ b/src/backend/executor/Makefile @@ -15,7 +15,7 @@ include $(top_builddir)/src/Makefile.global OBJS = execAmi.o execCurrent.o execGrouping.o execJunk.o execMain.o \ execProcnode.o execQual.o execScan.o execTuples.o \ execUtils.o functions.o instrument.o nodeAppend.o nodeAgg.o \ - nodeBitmapAnd.o nodeBitmapOr.o \ + nodeBitmapAnd.o nodeBitmapOr.o nodeCustom.o \ nodeBitmapHeapscan.o nodeBitmapIndexscan.o nodeHash.o \ nodeHashjoin.o nodeIndexscan.o nodeIndexonlyscan.o \ nodeLimit.o nodeLockRows.o \ diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c index a078104..f80e6c4 100644 --- a/src/backend/executor/execAmi.c +++ b/src/backend/executor/execAmi.c @@ -21,6 +21,7 @@ #include "executor/nodeBitmapIndexscan.h" #include "executor/nodeBitmapOr.h" #include "executor/nodeCtescan.h" +#include "executor/nodeCustom.h" #include "executor/nodeForeignscan.h" #include "executor/nodeFunctionscan.h" #include "executor/nodeGroup.h" @@ -197,6 +198,10 @@ ExecReScan(PlanState *node) ExecReScanForeignScan((ForeignScanState *) node); break; + case T_CustomScanState: + ExecReScanCustomScan((CustomScanState *) node); + break; + case T_NestLoopState: ExecReScanNestLoop((NestLoopState *) node); break; @@ -291,6 +296,10 @@ ExecMarkPos(PlanState *node) ExecValuesMarkPos((ValuesScanState *) node); break; + case T_CustomScanState: + ExecCustomMarkPos((CustomScanState *) node); + break; + case T_MaterialState: ExecMaterialMarkPos((MaterialState *) node); break; @@ -348,6 +357,10 @@ ExecRestrPos(PlanState *node) ExecValuesRestrPos((ValuesScanState *) node); break; + case T_CustomScanState: + ExecCustomRestrPos((CustomScanState *) node); + break; + case T_MaterialState: ExecMaterialRestrPos((MaterialState *) node); break; @@ -379,9 +392,9 @@ ExecRestrPos(PlanState *node) * and valuesscan support is actually useless code at present.) */ bool -ExecSupportsMarkRestore(NodeTag plantype) +ExecSupportsMarkRestore(Path *path) { - switch (plantype) + switch (path->pathtype) { case T_SeqScan: case T_IndexScan: @@ -392,6 +405,14 @@ ExecSupportsMarkRestore(NodeTag plantype) case T_Sort: return true; + case T_CustomPath: + { + int flags = ((CustomPath *) path)->custom_flags; + if (flags & CUSTOM__SUPPORT_MARK_RESTORE) + return true; + return false; + } + case T_Result: /* @@ -465,6 +486,15 @@ ExecSupportsBackwardScan(Plan *node) return ExecSupportsBackwardScan(((SubqueryScan *) node)->subplan) && TargetListSupportsBackwardScan(node->targetlist); + case T_CustomScan: + { + int flags = ((CustomScan *) node)->custom_flags; + + if (flags & CUSTOM__SUPPORT_BACKWARD_SCAN) + return TargetListSupportsBackwardScan(node->targetlist); + } + return false; + case T_Material: case T_Sort: /* these don't evaluate tlist */ diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c index 76dd62f..b1110b9 100644 --- a/src/backend/executor/execProcnode.c +++ b/src/backend/executor/execProcnode.c @@ -85,6 +85,7 @@ #include "executor/nodeBitmapIndexscan.h" #include "executor/nodeBitmapOr.h" #include "executor/nodeCtescan.h" +#include "executor/nodeCustom.h" #include "executor/nodeForeignscan.h" #include "executor/nodeFunctionscan.h" #include "executor/nodeGroup.h" @@ -244,6 +245,11 @@ ExecInitNode(Plan *node, EState *estate, int eflags) estate, eflags); break; + case T_CustomScan: + result = (PlanState *) ExecInitCustomScan((CustomScan *) node, + estate, eflags); + break; + /* * join nodes */ @@ -442,6 +448,10 @@ ExecProcNode(PlanState *node) result = ExecForeignScan((ForeignScanState *) node); break; + case T_CustomScanState: + result = ExecCustomScan((CustomScanState *) node); + break; + /* * join nodes */ @@ -678,6 +688,10 @@ ExecEndNode(PlanState *node) ExecEndForeignScan((ForeignScanState *) node); break; + case T_CustomScanState: + ExecEndCustomScan((CustomScanState *) node); + break; + /* * join nodes */ diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index 90c2753..e60ac67 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -592,7 +592,7 @@ ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext, slot = econtext->ecxt_outertuple; break; - /* INDEX_VAR is handled by default case */ + /* INDEX_VAR and CUSTOM_VAR are handled by default case */ default: /* get the tuple from the relation being * scanned */ @@ -680,7 +680,7 @@ ExecEvalScalarVarFast(ExprState *exprstate, ExprContext *econtext, slot = econtext->ecxt_outertuple; break; - /* INDEX_VAR is handled by default case */ + /* INDEX_VAR and CUSTOM_VAR are handled by default case */ default: /* get the tuple from the relation being * scanned */ @@ -732,7 +732,7 @@ ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate, ExprContext *econtext, slot = econtext->ecxt_outertuple; break; - /* INDEX_VAR is handled by default case */ + /* INDEX_VAR and CUSTOM_VAR are handled by default case */ default: /* get the tuple from the relation being * scanned */ @@ -915,7 +915,7 @@ ExecEvalWholeRowFast(WholeRowVarExprState *wrvstate, ExprContext *econtext, slot = econtext->ecxt_outertuple; break; - /* INDEX_VAR is handled by default case */ + /* INDEX_VAR and CUSTOM_VAR are handled by default case */ default: /* get the tuple from the relation being * scanned */ @@ -991,7 +991,7 @@ ExecEvalWholeRowSlow(WholeRowVarExprState *wrvstate, ExprContext *econtext, slot = econtext->ecxt_outertuple; break; - /* INDEX_VAR is handled by default case */ + /* INDEX_VAR and CUSTOM_VAR are handled by default case */ default: /* get the tuple from the relation being * scanned */ diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c index 39e3b2e..df0d295 100644 --- a/src/backend/executor/execUtils.c +++ b/src/backend/executor/execUtils.c @@ -578,7 +578,7 @@ ExecBuildProjectionInfo(List *targetList, projInfo->pi_lastOuterVar = attnum; break; - /* INDEX_VAR is handled by default case */ + /* INDEX_VAR and CUSTOM_VAR are handled by default case */ default: varSlotOffsets[numSimpleVars] = offsetof(ExprContext, @@ -638,7 +638,7 @@ get_last_attnums(Node *node, ProjectionInfo *projInfo) projInfo->pi_lastOuterVar = attnum; break; - /* INDEX_VAR is handled by default case */ + /* INDEX_VAR and CUSTOM_VAR are handled by default case */ default: if (projInfo->pi_lastScanVar < attnum) diff --git a/src/backend/executor/nodeCustom.c b/src/backend/executor/nodeCustom.c new file mode 100644 index 0000000..2d89d7a --- /dev/null +++ b/src/backend/executor/nodeCustom.c @@ -0,0 +1,252 @@ +/* ------------------------------------------------------------------------ + * + * nodeCustom.c + * Routines to handle execution of custom plan, scan and join node + * + * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * ------------------------------------------------------------------------ + */ +#include "postgres.h" + +#include "executor/nodeCustom.h" +#include "parser/parsetree.h" +#include "utils/hsearch.h" +#include "utils/memutils.h" +#include "utils/rel.h" + +/* static variables */ +static HTAB *custom_provider_hash = NULL; + +/* + * register_custom_provider + * + * It registers a custom execution provider; that consists of a set of + * callbacks and is identified with a unique name. + */ +void +register_custom_provider(const CustomProvider *provider) +{ + CustomProvider *entry; + bool found; + + if (!custom_provider_hash) + { + HASHCTL ctl; + + memset(&ctl, 0, sizeof(ctl)); + ctl.hcxt = CacheMemoryContext; + ctl.keysize = NAMEDATALEN; + ctl.entrysize = sizeof(CustomProvider); + + custom_provider_hash = hash_create("custom execution providers", + 32, + &ctl, + HASH_ELEM | HASH_CONTEXT); + } + + entry = hash_search(custom_provider_hash, + provider->name, + HASH_ENTER, &found); + if (found) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("duplicate custom execution provider \"%s\"", + provider->name))); + + Assert(strcmp(provider->name, entry->name) == 0); + memcpy(entry, provider, sizeof(CustomProvider)); +} + +/* + * get_custom_provider + * + * It finds a registered custom execution provide by its name + */ +CustomProvider * +get_custom_provider(const char *custom_name) +{ + CustomProvider *entry; + + /* lookup custom execution provider */ + if (!custom_provider_hash) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("no custom execution provider was registered"))); + + entry = (CustomProvider *) hash_search(custom_provider_hash, + custom_name, HASH_FIND, NULL); + if (!entry) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("custom execution provider \"%s\" was not registered", + custom_name))); + + return entry; +} + +/* + * ExecInitCustomScan + * + * Allocation of CustomScanState and various initialization stuff. + * Note that some of initialization jobs are skipped if scanrelid is zero + * (that means this custom scan plan is not associated with a particular + * relation in range-table list.) + */ +CustomScanState * +ExecInitCustomScan(CustomScan *node, EState *estate, int eflags) +{ + CustomProvider *provider = get_custom_provider(node->custom_name); + CustomScanState *csstate; + Plan *plan = &node->scan.plan; + Index scanrelid = node->scan.scanrelid; + + /* + * Create state structure + */ + csstate = makeNode(CustomScanState); + csstate->ss.ps.plan = plan; + csstate->ss.ps.state = estate; + csstate->custom_provider = provider; + csstate->custom_flags = node->custom_flags; + csstate->custom_state = NULL; + + /* + * Miscellaneous initialization + */ + ExecAssignExprContext(estate, &csstate->ss.ps); + + /* + * Initialization of child expressions + */ + csstate->ss.ps.targetlist = + (List *) ExecInitExpr((Expr *) plan->targetlist, &csstate->ss.ps); + csstate->ss.ps.qual = + (List *) ExecInitExpr((Expr *) plan->qual, &csstate->ss.ps); + + /* + * tuple table initialization + * + * Note that ss_ScanTupleSlot is set only when scanrelid is associated + * with a particular relation. Elsewhere, it needs to be initialized by + * custom-scan provider itself if it internally uses ss_ScanTupleSlot. + * If it replaces varno of Var node by CUSTOM_VAR, it has to be set to + * reference underlying attribute name to generate EXPLAIN output. + */ + ExecInitResultTupleSlot(estate, &csstate->ss.ps); + if (scanrelid > 0) + ExecInitScanTupleSlot(estate, &csstate->ss); + + /* + * open the base relation and acquire appropriate lock on it, + * if this custom scan is connected with a particular relaion. + * Also, assign its scan type according to the table definition. + */ + if (scanrelid > 0) + { + Relation rel = ExecOpenScanRelation(estate, scanrelid, eflags); + + csstate->ss.ss_currentRelation = rel; + ExecAssignScanType(&csstate->ss, RelationGetDescr(rel)); + + csstate->ss.ps.ps_TupFromTlist = false; + } + + /* + * Initialize result tuple type and projection info. + */ + ExecAssignResultTypeFromTL(&csstate->ss.ps); + + if (scanrelid > 0) + ExecAssignScanProjectionInfo(&csstate->ss); + else + ExecAssignProjectionInfo(&csstate->ss.ps, NULL); + + /* + * Final initialization based on callback of BeginCustomScan method. + * Extension may be able to override initialization stuff above, if + * needed. + */ + csstate->custom_provider->BeginCustomScan(csstate, eflags); + + return csstate; +} + +/* + * ExecCustomScan + * + * Just an entrypoint of ExecCustomScan method. All the stuff to fetch + * a tuple is a job of custom-scan provider. + */ +TupleTableSlot * +ExecCustomScan(CustomScanState *csstate) +{ + return csstate->custom_provider->ExecCustomScan(csstate); +} + +/* + * MultiExecCustomScan + * + * Aldo, just an entrypoint of MultiExecCustomScan method. All the stuff + * to fetch multiple tuples (according to expectation of upper node) is + * a job of custom-scan provider. + */ +Node * +MultiExecCustomScan(CustomScanState *csstate) +{ + return csstate->custom_provider->MultiExecCustomScan(csstate); +} + +/* + * ExecEndCustomScan + * + * It releases all the resources allocated on this scan. + */ +void +ExecEndCustomScan(CustomScanState *csstate) +{ + /* Let the custom-exec shut down */ + csstate->custom_provider->EndCustomScan(csstate); + + /* Free the exprcontext */ + ExecFreeExprContext(&csstate->ss.ps); + + /* Clean out the tuple table, if exists */ + ExecClearTuple(csstate->ss.ps.ps_ResultTupleSlot); + if (csstate->ss.ss_ScanTupleSlot) + ExecClearTuple(csstate->ss.ss_ScanTupleSlot); + + /* close the relation, if opened */ + if (csstate->ss.ss_currentRelation) + ExecCloseScanRelation(csstate->ss.ss_currentRelation); +} + +/* + * ExecReScanCustomScan + */ +void +ExecReScanCustomScan(CustomScanState *csstate) +{ + csstate->custom_provider->ReScanCustomScan(csstate); +} + +/* + * ExecCustomMarkPos + */ +void +ExecCustomMarkPos(CustomScanState *csstate) +{ + Assert((csstate->custom_flags & CUSTOM__SUPPORT_MARK_RESTORE) != 0); + csstate->custom_provider->MarkPosCustomScan(csstate); +} + +/* + * ExecCustomRestrPos + */ +void +ExecCustomRestrPos(CustomScanState *csstate) +{ + Assert((csstate->custom_flags & CUSTOM__SUPPORT_MARK_RESTORE) != 0); + csstate->custom_provider->RestorePosCustom(csstate); +} diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index e3edcf6..e21982f 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -598,6 +598,33 @@ _copyForeignScan(const ForeignScan *from) } /* + * _copyCustomScan + */ +static CustomScan * +_copyCustomScan(const CustomScan *from) +{ + CustomScan *newnode = makeNode(CustomScan); + + /* + * copy node superclass fields + */ + CopyScanFields((const Scan *) from, (Scan *) newnode); + + /* + * copy remainder of node + */ + COPY_STRING_FIELD(custom_name); + COPY_SCALAR_FIELD(custom_flags); + COPY_NODE_FIELD(custom_private); + COPY_NODE_FIELD(custom_exprs); + + COPY_NODE_FIELD(subqry_plan); + COPY_NODE_FIELD(functions); + + return newnode; +} + +/* * CopyJoinFields * * This function copies the fields of the Join node. It is used by @@ -3951,6 +3978,9 @@ copyObject(const void *from) case T_ForeignScan: retval = _copyForeignScan(from); break; + case T_CustomScan: + retval = _copyCustomScan(from); + break; case T_Join: retval = _copyJoin(from); break; diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 4c7505e..00c7466 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 +_outCustomScan(StringInfo str, const CustomScan *node) +{ + WRITE_NODE_TYPE("CUSTOMSCAN"); + + _outScanInfo(str, (const Scan *) node); + + WRITE_STRING_FIELD(custom_name); + WRITE_INT_FIELD(custom_flags); + WRITE_NODE_FIELD(custom_private); + WRITE_NODE_FIELD(custom_exprs); + + WRITE_NODE_FIELD(subqry_plan); + WRITE_NODE_FIELD(functions); +} + +static void _outJoin(StringInfo str, const Join *node) { WRITE_NODE_TYPE("JOIN"); @@ -2824,6 +2840,9 @@ _outNode(StringInfo str, const void *obj) case T_ForeignScan: _outForeignScan(str, obj); break; + case T_CustomScan: + _outCustomScan(str, obj); + break; case T_Join: _outJoin(str, obj); break; diff --git a/src/backend/nodes/print.c b/src/backend/nodes/print.c index 48ef325..29fcba9 100644 --- a/src/backend/nodes/print.c +++ b/src/backend/nodes/print.c @@ -333,6 +333,10 @@ print_expr(const Node *expr, const List *rtable) relname = "INDEX"; attname = "?"; break; + case CUSTOM_VAR: + relname = "CUSTOM"; + attname = "?"; + break; default: { RangeTblEntry *rte; diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 96fe50f..ebc0b28 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -47,6 +47,8 @@ int geqo_threshold; /* Hook for plugins to replace standard_join_search() */ join_search_hook_type join_search_hook = NULL; +/* Hook for plugins to add custom scan paths */ +add_scan_path_hook_type add_scan_path_hook = NULL; static void set_base_rel_sizes(PlannerInfo *root); static void set_base_rel_pathlists(PlannerInfo *root); @@ -400,6 +402,9 @@ set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) /* Consider TID scans */ create_tidscan_paths(root, rel); + /* Consider Custom scans */ + add_custom_scan_paths(root,rel,rte); + /* Now find the cheapest of the paths for this rel */ set_cheapest(rel); } @@ -428,6 +433,9 @@ set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) /* Call the FDW's GetForeignPaths function to generate path(s) */ rel->fdwroutine->GetForeignPaths(root, rel, rte->relid); + /* Consider Custom scans */ + add_custom_scan_paths(root,rel,rte); + /* Select cheapest path */ set_cheapest(rel); } @@ -1247,6 +1255,9 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel, /* Generate appropriate path */ add_path(rel, create_subqueryscan_path(root, rel, pathkeys, required_outer)); + /* Consider Custom scans */ + add_custom_scan_paths(root,rel,rte); + /* Select cheapest path (pretty easy in this case...) */ set_cheapest(rel); } @@ -1318,6 +1329,9 @@ set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) add_path(rel, create_functionscan_path(root, rel, pathkeys, required_outer)); + /* Consider Custom scans */ + add_custom_scan_paths(root,rel,rte); + /* Select cheapest path (pretty easy in this case...) */ set_cheapest(rel); } @@ -1341,6 +1355,9 @@ set_values_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) /* Generate appropriate path */ add_path(rel, create_valuesscan_path(root, rel, required_outer)); + /* Consider Custom scans */ + add_custom_scan_paths(root,rel,rte); + /* Select cheapest path (pretty easy in this case...) */ set_cheapest(rel); } @@ -1410,6 +1427,9 @@ set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) /* Generate appropriate path */ add_path(rel, create_ctescan_path(root, rel, required_outer)); + /* Consider Custom scans */ + add_custom_scan_paths(root,rel,rte); + /* Select cheapest path (pretty easy in this case...) */ set_cheapest(rel); } @@ -1463,6 +1483,9 @@ set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) /* Generate appropriate path */ add_path(rel, create_worktablescan_path(root, rel, required_outer)); + /* Consider Custom scans */ + add_custom_scan_paths(root,rel,rte); + /* Select cheapest path (pretty easy in this case...) */ set_cheapest(rel); } diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index 50f0852..c6010d9 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -2312,7 +2312,7 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path, * it off does not entitle us to deliver an invalid plan. */ else if (innersortkeys == NIL && - !ExecSupportsMarkRestore(inner_path->pathtype)) + !ExecSupportsMarkRestore(inner_path)) path->materialize_inner = true; /* diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c index 5b477e5..9483614 100644 --- a/src/backend/optimizer/path/joinpath.c +++ b/src/backend/optimizer/path/joinpath.c @@ -21,6 +21,8 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" +/* Hook for plugins to add custom join paths */ +add_join_path_hook_type add_join_path_hook = NULL; #define PATH_PARAM_BY_REL(path, rel) \ ((path)->param_info && bms_overlap(PATH_REQ_OUTER(path), (rel)->relids)) @@ -259,6 +261,22 @@ add_paths_to_joinrel(PlannerInfo *root, restrictlist, jointype, sjinfo, &semifactors, param_source_rels, extra_lateral_rels); + + /* + * 5. Also consider paths being provided with custom execution provider. + */ + if (add_join_path_hook) + (*add_join_path_hook)(root, + joinrel, + outerrel, + innerrel, + jointype, + sjinfo, + restrictlist, + mergeclause_list, + &semifactors, + param_source_rels, + extra_lateral_rels); } /* diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index f2c122d..a545af0 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -21,6 +21,7 @@ #include "access/skey.h" #include "catalog/pg_class.h" +#include "executor/nodeCustom.h" #include "foreign/fdwapi.h" #include "miscadmin.h" #include "nodes/makefuncs.h" @@ -77,6 +78,9 @@ static WorkTableScan *create_worktablescan_plan(PlannerInfo *root, Path *best_pa List *tlist, List *scan_clauses); static ForeignScan *create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path, List *tlist, List *scan_clauses); +static CustomScan *create_customscan_plan(PlannerInfo *root, + CustomPath *best_path, + List *tlist, List *scan_clauses); static NestLoop *create_nestloop_plan(PlannerInfo *root, NestPath *best_path, Plan *outer_plan, Plan *inner_plan); static MergeJoin *create_mergejoin_plan(PlannerInfo *root, MergePath *best_path, @@ -233,6 +237,7 @@ create_plan_recurse(PlannerInfo *root, Path *best_path) case T_CteScan: case T_WorkTableScan: case T_ForeignScan: + case T_CustomScan: plan = create_scan_plan(root, best_path); break; case T_HashJoin: @@ -409,6 +414,13 @@ create_scan_plan(PlannerInfo *root, Path *best_path) scan_clauses); break; + case T_CustomScan: + plan = (Plan *) create_customscan_plan(root, + (CustomPath *) best_path, + tlist, + scan_clauses); + break; + default: elog(ERROR, "unrecognized node type: %d", (int) best_path->pathtype); @@ -2009,6 +2021,98 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path, return scan_plan; } +/* + * create_customscan_plan + * Returns a custom-scan plan for the base relation scanned by 'best_path' + * with restriction clauses 'scan_clauses' and targetlist 'tlist'. + */ +static CustomScan * +create_customscan_plan(PlannerInfo *root, + CustomPath *best_path, + List *tlist, + List *scan_clauses) +{ + CustomProvider *provider = get_custom_provider(best_path->custom_name); + CustomScan *scan_plan = makeNode(CustomScan); + RelOptKind reloptkind = best_path->path.parent->reloptkind; + RangeTblEntry *rte; + Index scan_relid; + + if (reloptkind == RELOPT_BASEREL || + reloptkind == RELOPT_OTHER_MEMBER_REL) + { + scan_relid = best_path->path.parent->relid; + + rte = planner_rt_fetch(scan_relid, root); + /* + * For EXPLAIN output, we save various information in CustomScan plan + * structure. Custom-scan provider can utilize them, but it is not + * recommendablt to adjust. + */ + if (rte->rtekind == RTE_SUBQUERY) + { + if (best_path->path.param_info) + { + List *subplan_params + = best_path->path.parent->subplan_params; + process_subquery_nestloop_params(root, subplan_params); + } + scan_plan->subqry_plan = best_path->path.parent->subplan; + } + else if (rte->rtekind == RTE_FUNCTION) + { + List *functions = rte->functions; + + if (best_path->path.param_info) + functions = (List *) + replace_nestloop_params(root, (Node *)functions); + scan_plan->functions = functions; + } + } + else if (reloptkind == RELOPT_JOINREL) + scan_relid = 0; + else + elog(ERROR, "unexpected reloptkind: %d", (int)reloptkind); + + scan_clauses = order_qual_clauses(root, scan_clauses); + scan_plan->scan.plan.targetlist = NULL; /* to be set by callback */ + scan_plan->scan.plan.qual = NULL; /* to be set by callback */ + scan_plan->scan.plan.lefttree = NULL; + scan_plan->scan.plan.righttree = NULL; + scan_plan->scan.scanrelid = scan_relid; + + scan_plan->custom_name = pstrdup(best_path->custom_name); + scan_plan->custom_flags = best_path->custom_flags; + scan_plan->custom_private = NIL; + scan_plan->custom_exprs = NULL; + + /* + * Let custom scan provider perform to set up this custom-scan plan + * according to the given path information. + */ + provider->InitCustomScanPlan(root, scan_plan, + best_path, tlist, scan_clauses); + + /* Copy cost data from Path to Plan; no need to make callback do this */ + copy_path_costsize(&scan_plan->scan.plan, &best_path->path); + + /* + * Replace any outer-relation variables with nestloop params in the qual + * and custom_exprs expressions. We do this last so that the FDW doesn't + * have to be involved. (Note that parts of custom_exprs could have come + * from join clauses, so doing this beforehand on the scan_clauses + * wouldn't work.) + */ + if (best_path->path.param_info) + { + scan_plan->scan.plan.qual = (List *) + replace_nestloop_params(root, (Node *) scan_plan->scan.plan.qual); + scan_plan->custom_exprs = (List *) + replace_nestloop_params(root, (Node *) scan_plan->custom_exprs); + } + + return scan_plan; +} /***************************************************************************** * diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 5c9f3d6..1af5469 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -17,6 +17,7 @@ #include "access/transam.h" #include "catalog/pg_type.h" +#include "executor/nodeCustom.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/pathnode.h" @@ -575,6 +576,30 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) } break; + case T_CustomScan: + { + CustomScan *splan = (CustomScan *) plan; + CustomProvider *provider + = get_custom_provider(splan->custom_name); + + if (provider->SetPlanRefCustomScan) + provider->SetPlanRefCustomScan(root, splan, rtoffset); + else if (splan->scan.scanrelid > 0) + { + splan->scan.scanrelid += rtoffset; + splan->scan.plan.targetlist = + fix_scan_list(root, splan->scan.plan.targetlist, + rtoffset); + splan->scan.plan.qual = + fix_scan_list(root, splan->scan.plan.qual, rtoffset); + splan->custom_exprs = + fix_scan_list(root, splan->custom_exprs, rtoffset); + } + else + elog(ERROR, "No implementation to set plan reference"); + } + break; + case T_NestLoop: case T_MergeJoin: case T_HashJoin: diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c index d8cabbd..3a19aac 100644 --- a/src/backend/optimizer/plan/subselect.c +++ b/src/backend/optimizer/plan/subselect.c @@ -2222,6 +2222,16 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params, context.paramids = bms_add_members(context.paramids, scan_params); break; + case T_CustomScan: + finalize_primnode((Node *) ((CustomScan *) plan)->custom_exprs, + &context); + context.paramids = bms_add_members(context.paramids, scan_params); + /* + * XXX - Is it sufficient to do? Don't we need something special + * if CustomScan override FunctionScan or SubqueryScan. + */ + break; + case T_ModifyTable: { ModifyTable *mtplan = (ModifyTable *) plan; diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index a7169ef..32e8b59 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -1738,6 +1738,46 @@ create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel, } /* + * create_customscan_path + * Creates a path corresponding to a scan of a relation based on logic + * logic being provided by extensions. + * + * This function is never called from core PostgreSQL. An usual usage is + * invocation from callbacks on add_scan_path_hook. We don't have any + * assumption on the custom scan logic, thus, caller is responsible to + * set adequate cost estimation here. + */ +CustomPath * +create_customscan_path(PlannerInfo *root, + RelOptInfo *baserel, + double rows, + Cost startup_cost, + Cost total_cost, + List *pathkeys, + Relids required_outer, + const char *custom_name, + uint32 custom_flags, + List *custom_private) +{ + CustomPath *pathnode = makeNode(CustomPath); + + pathnode->path.pathtype = T_CustomScan; + pathnode->path.parent = baserel; + pathnode->path.param_info = get_baserel_parampathinfo(root, baserel, + required_outer); + pathnode->path.rows = rows; + pathnode->path.startup_cost = startup_cost; + pathnode->path.total_cost = total_cost; + pathnode->path.pathkeys = pathkeys; + + pathnode->custom_name = pstrdup(custom_name); + pathnode->custom_flags = custom_flags; + pathnode->custom_private = custom_private; + + return pathnode; +} + +/* * calc_nestloop_required_outer * Compute the required_outer set for a nestloop join path * diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 348f620..48bd672 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -143,6 +143,7 @@ typedef struct List *outer_tlist; /* referent for OUTER_VAR Vars */ List *inner_tlist; /* referent for INNER_VAR Vars */ List *index_tlist; /* referent for INDEX_VAR Vars */ + TupleDesc custom_tupdesc; /* referent for CUSTOM_VAR Vars */ } deparse_namespace; /* @@ -2447,14 +2448,19 @@ deparse_context_for(const char *aliasname, Oid relid) * deparse_context_for_planstate - Build deparse context for a plan * * When deparsing an expression in a Plan tree, we might have to resolve - * OUTER_VAR, INNER_VAR, or INDEX_VAR references. To do this, the caller must - * provide the parent PlanState node. Then OUTER_VAR and INNER_VAR references - * can be resolved by drilling down into the left and right child plans. + * special varno (OUTER_VAR, INNER_VAR, INDEX_VAR or CUSTOM_VAR) references. + * To do this, the caller must provide the parent PlanState node. Then + * OUTER_VAR and INNER_VAR references can be resolved by drilling down into + * the left and right child plans. * Similarly, INDEX_VAR references can be resolved by reference to the * indextlist given in the parent IndexOnlyScan node. (Note that we don't * currently support deparsing of indexquals in regular IndexScan or * BitmapIndexScan nodes; for those, we can only deparse the indexqualorig * fields, which won't contain INDEX_VAR Vars.) + * Also, CUSTOM_VAR references can be resolved by reference to the TupleDesc + * of ss_ScanTupleSlot in CustomScanState node. (Note that custom scan + * provider must be responsible to initialize the ss_ScanTupleSlot with + * appropriate TupleDesc; being likely constructed by ExecTypeFromTL). * * Note: planstate really ought to be declared as "PlanState *", but we use * "Node *" to avoid having to include execnodes.h in builtins.h. @@ -3712,6 +3718,14 @@ set_deparse_planstate(deparse_namespace *dpns, PlanState *ps) dpns->index_tlist = ((IndexOnlyScan *) ps->plan)->indextlist; else dpns->index_tlist = NIL; + + /* custom_tupdesc is set only if it's an CustomScan */ + if (IsA(ps, CustomScanState) && + ((CustomScanState *)ps)->ss.ss_ScanTupleSlot) + dpns->custom_tupdesc = + ((CustomScanState *)ps)->ss.ss_ScanTupleSlot->tts_tupleDescriptor; + else + dpns->custom_tupdesc = NULL; } /* @@ -5379,6 +5393,18 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context) return NULL; } + else if (var->varno == CUSTOM_VAR && dpns->custom_tupdesc) + { + TupleDesc tupdesc = dpns->custom_tupdesc; + + Assert(netlevelsup == 0); + Assert(var->varattno > 0 && var->varattno <= tupdesc->natts); + + attname = NameStr(tupdesc->attrs[var->varattno - 1]->attname); + appendStringInfoString(buf, quote_identifier(attname)); + + return attname; + } else { elog(ERROR, "bogus varno: %d", var->varno); @@ -5649,6 +5675,18 @@ get_name_for_var_field(Var *var, int fieldno, return result; } + else if (var->varno == CUSTOM_VAR && dpns->custom_tupdesc) + { + TupleDesc tupdesc = dpns->custom_tupdesc; + const char *result; + + Assert(netlevelsup == 0); + Assert(var->varattno > 0 && var->varattno <= tupdesc->natts); + + result = NameStr(tupdesc->attrs[var->varattno - 1]->attname); + + return result; + } else { elog(ERROR, "bogus varno: %d", var->varno); diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 75841c8..51537d2 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -16,6 +16,7 @@ #include "executor/execdesc.h" #include "nodes/parsenodes.h" +#include "nodes/relation.h" /* @@ -102,7 +103,7 @@ extern PGDLLIMPORT ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook; extern void ExecReScan(PlanState *node); extern void ExecMarkPos(PlanState *node); extern void ExecRestrPos(PlanState *node); -extern bool ExecSupportsMarkRestore(NodeTag plantype); +extern bool ExecSupportsMarkRestore(Path *path); extern bool ExecSupportsBackwardScan(Plan *node); extern bool ExecMaterializesOutput(NodeTag plantype); diff --git a/src/include/executor/nodeCustom.h b/src/include/executor/nodeCustom.h new file mode 100644 index 0000000..a484f8b --- /dev/null +++ b/src/include/executor/nodeCustom.h @@ -0,0 +1,94 @@ +/* ------------------------------------------------------------------------ + * + * nodeCustom.h + * + * prototypes for CustomScan nodes + * + * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * ------------------------------------------------------------------------ + */ +#ifndef NODECUSTOM_H +#define NODECUSTOM_H +#include "commands/explain.h" +#include "nodes/plannodes.h" +#include "nodes/relation.h" + +/* + * Definition of the custom execution provider callbacks + */ +typedef void (*InitCustomScanPlan_function)(PlannerInfo *root, + CustomScan *cscan_plan, + CustomPath *cscan_path, + List *tlist, + List *scan_clauses); +typedef void (*SetPlanRefCustomScan_function)(PlannerInfo *root, + CustomScan *cscan_plan, + int rtoffset); +typedef void (*BeginCustomScan_function)(CustomScanState *csstate, int eflags); +typedef TupleTableSlot *(*ExecCustomScan_function)(CustomScanState *csstate); +typedef Node *(*MultiExecCustomScan_function)(CustomScanState *csstate); +typedef void (*EndCustomScan_function)(CustomScanState *csstate); + +typedef void (*ReScanCustomScan_function)(CustomScanState *csstate); +typedef void (*MarkPosCustomScan_function)(CustomScanState *csstate); +typedef void (*RestorePosCustom_function)(CustomScanState *csstate); + +typedef void (*ExplainCustomScan_function)(CustomScanState *csstate, + ExplainState *es); + +typedef struct CustomProvider +{ + char name[NAMEDATALEN]; + + InitCustomScanPlan_function InitCustomScanPlan; + SetPlanRefCustomScan_function SetPlanRefCustomScan; + + BeginCustomScan_function BeginCustomScan; + ExecCustomScan_function ExecCustomScan; + MultiExecCustomScan_function MultiExecCustomScan; + EndCustomScan_function EndCustomScan; + + ReScanCustomScan_function ReScanCustomScan; + MarkPosCustomScan_function MarkPosCustomScan; + RestorePosCustom_function RestorePosCustom; + + ExplainCustomScan_function ExplainCustomScan; +} CustomProvider; + +/* Flags of CustomScan */ + +/* + * CUSTOM__SUPPORT_MARK_RESTORE informs optimizer this custom scan provider + * support ExecCustomMarkPos and ExecCustomRestrPos callbacks. + */ +#define CUSTOM__SUPPORT_MARK_RESTORE 0x0001 + +/* + * CUSTOM__SUPPORT_BACKWARD_SCAN informs optimizer this custom scan provider + * is designed to support backward scan. + */ +#define CUSTOM__SUPPORT_BACKWARD_SCAN 0x0002 + +/* + * Registration and lookup custom execution provider + */ +extern void register_custom_provider(const CustomProvider *provider); + +extern CustomProvider *get_custom_provider(const char *custom_name); + +/* + * General executor code + */ +extern CustomScanState *ExecInitCustomScan(CustomScan *csstate, + EState *estate, int eflags); +extern TupleTableSlot *ExecCustomScan(CustomScanState *csstate); +extern Node *MultiExecCustomScan(CustomScanState *csstate); +extern void ExecEndCustomScan(CustomScanState *csstate); + +extern void ExecReScanCustomScan(CustomScanState *csstate); +extern void ExecCustomMarkPos(CustomScanState *csstate); +extern void ExecCustomRestrPos(CustomScanState *csstate); + +#endif /* NODECUSTOM_H */ diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 5a40347..f315b8f 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -1496,6 +1496,23 @@ typedef struct ForeignScanState void *fdw_state; /* foreign-data wrapper can keep state here */ } ForeignScanState; +/* ---------------- + * CustomScanState information + * + * CustomScan nodes are used to scan various relations using custom + * logic. + * ---------------- + */ +typedef struct CustomScanState +{ + ScanState ss; + + /* use struct pointer to avoid including nodeCustom.h here */ + struct CustomProvider *custom_provider; + int custom_flags; + void *custom_state; +} CustomScanState; + /* ---------------------------------------------------------------- * Join State Information * ---------------------------------------------------------------- diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index ff9af76..adc5123 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -62,6 +62,7 @@ typedef enum NodeTag T_CteScan, T_WorkTableScan, T_ForeignScan, + T_CustomScan, T_Join, T_NestLoop, T_MergeJoin, @@ -107,6 +108,7 @@ typedef enum NodeTag T_CteScanState, T_WorkTableScanState, T_ForeignScanState, + T_CustomScanState, T_JoinState, T_NestLoopState, T_MergeJoinState, @@ -224,6 +226,7 @@ typedef enum NodeTag T_HashPath, T_TidPath, T_ForeignPath, + T_CustomPath, T_AppendPath, T_MergeAppendPath, T_ResultPath, diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 101e22c..58575b9 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -479,6 +479,22 @@ typedef struct ForeignScan bool fsSystemCol; /* true if any "system column" is needed */ } ForeignScan; +/* ---------------- + * CustomScan node + * ---------------- + */ +typedef struct CustomScan +{ + Scan scan; + + const char *custom_name; /* name of custom scan provider */ + int custom_flags; /* a set of CUSTOM__* flags */ + List *custom_private; /* private data for CSP */ + List *custom_exprs; /* expressions that CSP may execute */ + + Plan *subqry_plan; /* valid, if RTE_SUBQUERY */ + List *functions; /* valid, if RTE_FUNCTION */ +} CustomScan; /* * ========== diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 7918537..b71c7ca 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -134,6 +134,7 @@ typedef struct Expr #define INNER_VAR 65000 /* reference to inner subplan */ #define OUTER_VAR 65001 /* reference to outer subplan */ #define INDEX_VAR 65002 /* reference to index column */ +#define CUSTOM_VAR 65003 /* reference to custom column */ #define IS_SPECIAL_VARNO(varno) ((varno) >= INNER_VAR) diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 6d7b594..50194f2 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -876,6 +876,22 @@ typedef struct ForeignPath } ForeignPath; /* + * CustomPath represents a scan using custom logic + * + * custom_name is the identifier of custom scan provider when it was + * registered. custom_flags is a set of CUSTOM__* bits to control its + * behavior. custom_private allows extension to store its private data + * but has to be safe for copyObject(). + */ +typedef struct CustomPath +{ + Path path; + const char *custom_name; /* name of custom scan provider */ + int custom_flags; /* CUSTOM__* flags in nodeCustom.h */ + List *custom_private; /* can be used for private data */ +} CustomPath; + +/* * AppendPath represents an Append plan, ie, successive execution of * several member plans. * diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index 0033a3c..8fbdb66 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -82,6 +82,16 @@ extern ForeignPath *create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel, List *pathkeys, Relids required_outer, List *fdw_private); +extern CustomPath *create_customscan_path(PlannerInfo *root, + RelOptInfo *baserel, + double rows, + Cost startup_cost, + Cost total_cost, + List *pathkeys, + Relids required_outer, + const char *custom_name, + uint32 custom_flags, + List *custom_private); extern Relids calc_nestloop_required_outer(Path *outer_path, Path *inner_path); extern Relids calc_non_nestloop_required_outer(Path *outer_path, Path *inner_path); diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index 999adaa..09406f4 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -29,6 +29,31 @@ typedef RelOptInfo *(*join_search_hook_type) (PlannerInfo *root, List *initial_rels); extern PGDLLIMPORT join_search_hook_type join_search_hook; +/* Hook for plugins to add custom scan path, in addition to default ones */ +typedef void (*add_scan_path_hook_type)(PlannerInfo *root, + RelOptInfo *baserel, + RangeTblEntry *rte); +extern PGDLLIMPORT add_scan_path_hook_type add_scan_path_hook; + +#define add_custom_scan_paths(root,baserel,rte) \ + do { \ + if (add_scan_path_hook) \ + (*add_scan_path_hook)((root),(baserel),(rte)); \ + } while(0) + +/* Hook for plugins to add custom join path, in addition to default ones */ +typedef void (*add_join_path_hook_type)(PlannerInfo *root, + RelOptInfo *joinrel, + RelOptInfo *outerrel, + RelOptInfo *innerrel, + JoinType jointype, + SpecialJoinInfo *sjinfo, + List *restrictlist, + List *mergeclause_list, + SemiAntiJoinFactors *semifactors, + Relids param_source_rels, + Relids extra_lateral_rels); +extern PGDLLIMPORT add_join_path_hook_type add_join_path_hook; extern RelOptInfo *make_one_rel(PlannerInfo *root, List *joinlist); extern RelOptInfo *standard_join_search(PlannerInfo *root, int levels_needed,