doc/src/sgml/custom-scan.sgml | 43 +++++ doc/src/sgml/fdwhandler.sgml | 54 ++++++ src/backend/commands/explain.c | 15 +- src/backend/executor/execScan.c | 4 + src/backend/executor/nodeCustom.c | 38 ++++- src/backend/executor/nodeForeignscan.c | 34 ++-- src/backend/foreign/foreign.c | 32 +++- src/backend/nodes/bitmapset.c | 57 +++++++ src/backend/nodes/copyfuncs.c | 5 + src/backend/nodes/outfuncs.c | 5 + src/backend/optimizer/geqo/geqo_eval.c | 3 + src/backend/optimizer/path/allpaths.c | 292 ++++++++++++++++++++++++++++++++ src/backend/optimizer/path/joinpath.c | 13 ++ src/backend/optimizer/plan/createplan.c | 80 +++++++-- src/backend/optimizer/plan/setrefs.c | 64 +++++++ src/backend/optimizer/util/plancat.c | 7 +- src/backend/optimizer/util/relnode.c | 14 ++ src/backend/utils/adt/ruleutils.c | 4 + src/include/foreign/fdwapi.h | 8 + src/include/nodes/bitmapset.h | 1 + src/include/nodes/plannodes.h | 24 ++- src/include/nodes/relation.h | 2 + src/include/optimizer/paths.h | 21 +++ src/include/optimizer/planmain.h | 1 + 24 files changed, 772 insertions(+), 49 deletions(-) diff --git a/doc/src/sgml/custom-scan.sgml b/doc/src/sgml/custom-scan.sgml index 8a4a3df..b1400ae 100644 --- a/doc/src/sgml/custom-scan.sgml +++ b/doc/src/sgml/custom-scan.sgml @@ -48,6 +48,27 @@ extern PGDLLIMPORT set_rel_pathlist_hook_type set_rel_pathlist_hook; + A custom scan provider will be also able to add paths by setting the + following hook, to replace built-in join paths by custom-scan that + performs as if a scan on preliminary joined relations, which us called + after the core code has generated what it believes to be the complete + and correct set of access paths for the join. + +typedef void (*set_join_pathlist_hook_type) (PlannerInfo *root, + RelOptInfo *joinrel, + RelOptInfo *outerrel, + RelOptInfo *innerrel, + List *restrictlist, + JoinType jointype, + SpecialJoinInfo *sjinfo, + SemiAntiJoinFactors *semifactors, + Relids param_source_rels, + Relids extra_lateral_rels); +extern PGDLLIMPORT set_join_pathlist_hook_type set_join_pathlist_hook; + + + + Although this hook function can be used to examine, modify, or remove paths generated by the core system, a custom scan provider will typically confine itself to generating CustomPath objects and adding @@ -124,7 +145,9 @@ typedef struct CustomScan Scan scan; uint32 flags; List *custom_exprs; + List *custom_ps_tlist; List *custom_private; + List *custom_relids; const CustomScanMethods *methods; } CustomScan; @@ -141,10 +164,30 @@ typedef struct CustomScan is only used by the custom scan provider itself. Plan trees must be able to be duplicated using copyObject, so all the data stored within these two fields must consist of nodes that function can handle. + custom_relids is set by the backend, thus custom-scan provider + does not need to touch, to track underlying relations represented by this + custom-scan node. methods must point to a (usually statically allocated) object implementing the required custom scan methods, which are further detailed below. + + In case when CustomScan replaced built-in join paths, + custom-scan provider must have two characteristic setup. + The first one is zero on the scan.scanrelid, which + should be usually an index of range-tables. It informs the backend + this CustomScan node is not associated with a particular + table. The second one is valid list of TargetEntry on + the custom_ps_tlist. A CustomScan node + looks to the backend like a scan as literal, but on a relation which is + the result of relations join. It means we cannot construct a tuple + descriptor based on table definition, thus custom-scan provider must + introduce the expected record-type of the tuples. + Tuple-descriptor of scan-slot shall be constructed based on the + custom_ps_tlist, and assigned on executor initialization. + Also, referenced by EXPLAIN to solve name of the underlying + columns and relations. + Custom Scan Callbacks diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml index c1daa4b..77477c8 100644 --- a/doc/src/sgml/fdwhandler.sgml +++ b/doc/src/sgml/fdwhandler.sgml @@ -598,6 +598,60 @@ IsForeignRelUpdatable (Relation rel); + + FDW Routines for remote join + + +void +GetForeignJoinPaths(PlannerInfo *root, + RelOptInfo *joinrel, + RelOptInfo *outerrel, + RelOptInfo *innerrel, + JoinType jointype, + SpecialJoinInfo *sjinfo, + SemiAntiJoinFactors *semifactors, + List *restrictlist, + Relids extra_lateral_rels); + + Create possible access paths for a join of two foreign tables or + joined relations, but both of them needs to be managed with same + FDW driver. + This optional function is called during query planning. + + + This function allows FDW driver to add ForeignScan path + towards the supplied joinrel. From the standpoint of + query planner, it looks like scan-node is added for join-relation. + It means, ForeignScan path added instead of the built-in + local join logic has to generate tuples as if it scans on a joined + and materialized relations. + + + Usually, we expect FDW drivers issues a remote query that involves + tables join on remote side, then FDW driver fetches the joined result + on local side. + Unlike simple table scan, definition of slot descriptor of the joined + relations is determined on the fly, thus we cannot know its definition + from the system catalog. + So, FDW driver is responsible to introduce the query planner expected + form of the joined relations. In case when ForeignScan + replaced a relations join, scanrelid of the generated plan + node shall be zero, to mark this ForeignScan node is not + associated with a particular foreign tables. + Also, it need to construct pseudo scan tlist (fdw_ps_tlist) + to indicate expected tuple definition. + + + Once scanrelid equals zero, executor initializes the slot + for scan according to fdw_ps_tlist, but excludes junk + entries. This list is also used to solve the name of the original + relation and columns, so FDW can chains expression nodes which are + not run on local side actually, like a join clause to be executed on + the remote side, however, target-entries of them will have + resjunk=true. + + + FDW Routines for <command>EXPLAIN</> diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index a951c55..8892dca 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -730,11 +730,17 @@ ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used) case T_ValuesScan: case T_CteScan: case T_WorkTableScan: - case T_ForeignScan: - case T_CustomScan: *rels_used = bms_add_member(*rels_used, ((Scan *) plan)->scanrelid); break; + case T_ForeignScan: + *rels_used = bms_add_members(*rels_used, + ((ForeignScan *) plan)->fdw_relids); + break; + case T_CustomScan: + *rels_used = bms_add_members(*rels_used, + ((CustomScan *) plan)->custom_relids); + break; case T_ModifyTable: *rels_used = bms_add_member(*rels_used, ((ModifyTable *) plan)->nominalRelation); @@ -1072,9 +1078,12 @@ ExplainNode(PlanState *planstate, List *ancestors, case T_ValuesScan: case T_CteScan: case T_WorkTableScan: + ExplainScanTarget((Scan *) plan, es); + break; case T_ForeignScan: case T_CustomScan: - ExplainScanTarget((Scan *) plan, es); + if (((Scan *) plan)->scanrelid > 0) + ExplainScanTarget((Scan *) plan, es); break; case T_IndexScan: { diff --git a/src/backend/executor/execScan.c b/src/backend/executor/execScan.c index 3f0d809..2f18a8a 100644 --- a/src/backend/executor/execScan.c +++ b/src/backend/executor/execScan.c @@ -251,6 +251,10 @@ ExecAssignScanProjectionInfo(ScanState *node) /* Vars in an index-only scan's tlist should be INDEX_VAR */ if (IsA(scan, IndexOnlyScan)) varno = INDEX_VAR; + /* Also foreign-/custom-scan on pseudo relation should be INDEX_VAR */ + else if (scan->scanrelid == 0 && + (IsA(scan, ForeignScan) || IsA(scan, CustomScan))) + varno = INDEX_VAR; else varno = scan->scanrelid; diff --git a/src/backend/executor/nodeCustom.c b/src/backend/executor/nodeCustom.c index b07932b..2344129 100644 --- a/src/backend/executor/nodeCustom.c +++ b/src/backend/executor/nodeCustom.c @@ -23,6 +23,7 @@ CustomScanState * ExecInitCustomScan(CustomScan *cscan, EState *estate, int eflags) { CustomScanState *css; + Index scan_relid = cscan->scan.scanrelid; Relation scan_rel; /* populate a CustomScanState according to the CustomScan */ @@ -48,12 +49,31 @@ ExecInitCustomScan(CustomScan *cscan, EState *estate, int eflags) ExecInitScanTupleSlot(estate, &css->ss); ExecInitResultTupleSlot(estate, &css->ss.ps); - /* initialize scan relation */ - scan_rel = ExecOpenScanRelation(estate, cscan->scan.scanrelid, eflags); - css->ss.ss_currentRelation = scan_rel; - css->ss.ss_currentScanDesc = NULL; /* set by provider */ - ExecAssignScanType(&css->ss, RelationGetDescr(scan_rel)); - + /* + * open the base relation and acquire appropriate lock on it, then + * get the scan type from the relation descriptor, if this custom + * scan is on actual relations. + * + * on the other hands, custom-scan may scan on a pseudo relation; + * that is usually a result-set of relations join by external + * computing resource, or others. It has to get the scan type from + * the pseudo-scan target-list that should be assigned by custom-scan + * provider. + */ + if (scan_relid > 0) + { + scan_rel = ExecOpenScanRelation(estate, scan_relid, eflags); + css->ss.ss_currentRelation = scan_rel; + css->ss.ss_currentScanDesc = NULL; /* set by provider */ + ExecAssignScanType(&css->ss, RelationGetDescr(scan_rel)); + } + else + { + TupleDesc ps_tupdesc; + + ps_tupdesc = ExecCleanTypeFromTL(cscan->custom_ps_tlist, false); + ExecAssignScanType(&css->ss, ps_tupdesc); + } css->ss.ps.ps_TupFromTlist = false; /* @@ -89,11 +109,11 @@ ExecEndCustomScan(CustomScanState *node) /* Clean out the tuple table */ ExecClearTuple(node->ss.ps.ps_ResultTupleSlot); - if (node->ss.ss_ScanTupleSlot) - ExecClearTuple(node->ss.ss_ScanTupleSlot); + ExecClearTuple(node->ss.ss_ScanTupleSlot); /* Close the heap relation */ - ExecCloseScanRelation(node->ss.ss_currentRelation); + if (node->ss.ss_currentRelation) + ExecCloseScanRelation(node->ss.ss_currentRelation); } void diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c index 7399053..542d176 100644 --- a/src/backend/executor/nodeForeignscan.c +++ b/src/backend/executor/nodeForeignscan.c @@ -102,6 +102,7 @@ ForeignScanState * ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags) { ForeignScanState *scanstate; + Index scanrelid = node->scan.scanrelid; Relation currentRelation; FdwRoutine *fdwroutine; @@ -141,16 +142,28 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags) ExecInitScanTupleSlot(estate, &scanstate->ss); /* - * open the base relation and acquire appropriate lock on it. + * open the base relation and acquire appropriate lock on it, then + * get the scan type from the relation descriptor, if this foreign + * scan is on actual foreign-table. + * + * on the other hands, foreign-scan may scan on a pseudo relation; + * that is usually a result-set of remote relations join. It has + * to get the scan type from the pseudo-scan target-list that should + * be assigned by FDW driver. */ - currentRelation = ExecOpenScanRelation(estate, node->scan.scanrelid, eflags); - scanstate->ss.ss_currentRelation = currentRelation; + if (scanrelid > 0) + { + currentRelation = ExecOpenScanRelation(estate, scanrelid, eflags); + scanstate->ss.ss_currentRelation = currentRelation; + ExecAssignScanType(&scanstate->ss, RelationGetDescr(currentRelation)); + } + else + { + TupleDesc ps_tupdesc; - /* - * get the scan type from the relation descriptor. (XXX at some point we - * might want to let the FDW editorialize on the scan tupdesc.) - */ - ExecAssignScanType(&scanstate->ss, RelationGetDescr(currentRelation)); + ps_tupdesc = ExecCleanTypeFromTL(node->fdw_ps_tlist, false); + ExecAssignScanType(&scanstate->ss, ps_tupdesc); + } /* * Initialize result tuple type and projection info. @@ -161,7 +174,7 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags) /* * Acquire function pointers from the FDW's handler, and init fdw_state. */ - fdwroutine = GetFdwRoutineForRelation(currentRelation, true); + fdwroutine = GetFdwRoutine(node->fdw_handler); scanstate->fdwroutine = fdwroutine; scanstate->fdw_state = NULL; @@ -193,7 +206,8 @@ ExecEndForeignScan(ForeignScanState *node) ExecClearTuple(node->ss.ss_ScanTupleSlot); /* close the relation. */ - ExecCloseScanRelation(node->ss.ss_currentRelation); + if (node->ss.ss_currentRelation) + ExecCloseScanRelation(node->ss.ss_currentRelation); } /* ---------------------------------------------------------------- diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c index cbe8b78..df69a95 100644 --- a/src/backend/foreign/foreign.c +++ b/src/backend/foreign/foreign.c @@ -302,13 +302,12 @@ GetFdwRoutine(Oid fdwhandler) return routine; } - /* - * GetFdwRoutineByRelId - look up the handler of the foreign-data wrapper - * for the given foreign table, and retrieve its FdwRoutine struct. + * GetFdwHandlerByRelId - look up the handler of the foreign-data wrapper + * for the given foreign table */ -FdwRoutine * -GetFdwRoutineByRelId(Oid relid) +static Oid +GetFdwHandlerByRelId(Oid relid) { HeapTuple tp; Form_pg_foreign_data_wrapper fdwform; @@ -350,7 +349,18 @@ GetFdwRoutineByRelId(Oid relid) ReleaseSysCache(tp); - /* And finally, call the handler function. */ + return fdwhandler; +} + +/* + * GetFdwRoutineByRelId - look up the handler of the foreign-data wrapper + * for the given foreign table, and retrieve its FdwRoutine struct. + */ +FdwRoutine * +GetFdwRoutineByRelId(Oid relid) +{ + Oid fdwhandler = GetFdwHandlerByRelId(relid); + return GetFdwRoutine(fdwhandler); } @@ -398,6 +408,16 @@ GetFdwRoutineForRelation(Relation relation, bool makecopy) return relation->rd_fdwroutine; } +/* + * GetFdwHandlerForRelation + * + * returns OID of FDW handler which is associated with the given relation. + */ +Oid +GetFdwHandlerForRelation(Relation relation) +{ + return GetFdwHandlerByRelId(RelationGetRelid(relation)); +} /* * IsImportableForeignTable - filter table names for IMPORT FOREIGN SCHEMA diff --git a/src/backend/nodes/bitmapset.c b/src/backend/nodes/bitmapset.c index a9c3b4b..4dc3286 100644 --- a/src/backend/nodes/bitmapset.c +++ b/src/backend/nodes/bitmapset.c @@ -301,6 +301,63 @@ bms_difference(const Bitmapset *a, const Bitmapset *b) } /* + * bms_shift_members - move all the bits by shift + */ +Bitmapset * +bms_shift_members(const Bitmapset *a, int shift) +{ + Bitmapset *b; + bitmapword h_word; + bitmapword l_word; + int nwords; + int w_shift; + int b_shift; + int i, j; + + /* fast path if result shall be NULL obviously */ + if (a == NULL || a->nwords * BITS_PER_BITMAPWORD + shift <= 0) + return NULL; + /* actually, not shift members */ + if (shift == 0) + return bms_copy(a); + + nwords = (a->nwords * BITS_PER_BITMAPWORD + shift + + BITS_PER_BITMAPWORD - 1) / BITS_PER_BITMAPWORD; + b = palloc(BITMAPSET_SIZE(nwords)); + b->nwords = nwords; + + if (shift > 0) + { + /* Left shift */ + w_shift = WORDNUM(shift); + b_shift = BITNUM(shift); + + for (i=0, j=-w_shift; i < b->nwords; i++, j++) + { + h_word = (j >= 0 && j < a->nwords ? a->words[j] : 0); + l_word = (j-1 >= 0 && j-1 < a->nwords ? a->words[j-1] : 0); + b->words[i] = ((h_word << b_shift) | + (l_word >> (BITS_PER_BITMAPWORD - b_shift))); + } + } + else + { + /* Right shift */ + w_shift = WORDNUM(-shift); + b_shift = BITNUM(-shift); + + for (i=0, j=-w_shift; i < b->nwords; i++, j++) + { + h_word = (j+1 >= 0 && j+1 < a->nwords ? a->words[j+1] : 0); + l_word = (j >= 0 && j < a->nwords ? a->words[j] : 0); + b->words[i] = ((h_word >> (BITS_PER_BITMAPWORD - b_shift)) | + (l_word << b_shift)); + } + } + return b; +} + +/* * bms_is_subset - is A a subset of B? */ bool diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 029761e..61379a7 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -592,8 +592,11 @@ _copyForeignScan(const ForeignScan *from) /* * copy remainder of node */ + COPY_SCALAR_FIELD(fdw_handler); COPY_NODE_FIELD(fdw_exprs); + COPY_NODE_FIELD(fdw_ps_tlist); COPY_NODE_FIELD(fdw_private); + COPY_BITMAPSET_FIELD(fdw_relids); COPY_SCALAR_FIELD(fsSystemCol); return newnode; @@ -617,7 +620,9 @@ _copyCustomScan(const CustomScan *from) */ COPY_SCALAR_FIELD(flags); COPY_NODE_FIELD(custom_exprs); + COPY_NODE_FIELD(custom_ps_tlist); COPY_NODE_FIELD(custom_private); + COPY_BITMAPSET_FIELD(custom_relids); /* * NOTE: The method field of CustomScan is required to be a pointer to a diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 385b289..a178132 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -558,8 +558,11 @@ _outForeignScan(StringInfo str, const ForeignScan *node) _outScanInfo(str, (const Scan *) node); + WRITE_OID_FIELD(fdw_handler); WRITE_NODE_FIELD(fdw_exprs); + WRITE_NODE_FIELD(fdw_ps_tlist); WRITE_NODE_FIELD(fdw_private); + WRITE_BITMAPSET_FIELD(fdw_relids); WRITE_BOOL_FIELD(fsSystemCol); } @@ -572,7 +575,9 @@ _outCustomScan(StringInfo str, const CustomScan *node) WRITE_UINT_FIELD(flags); WRITE_NODE_FIELD(custom_exprs); + WRITE_NODE_FIELD(custom_ps_tlist); WRITE_NODE_FIELD(custom_private); + WRITE_BITMAPSET_FIELD(custom_relids); appendStringInfoString(str, " :methods "); _outToken(str, node->methods->CustomName); if (node->methods->TextOutCustomScan) diff --git a/src/backend/optimizer/geqo/geqo_eval.c b/src/backend/optimizer/geqo/geqo_eval.c index d9a20da..efa64cf 100644 --- a/src/backend/optimizer/geqo/geqo_eval.c +++ b/src/backend/optimizer/geqo/geqo_eval.c @@ -266,6 +266,9 @@ merge_clump(PlannerInfo *root, List *clumps, Clump *new_clump, bool force) /* Keep searching if join order is not valid */ if (joinrel) { + /* Add extra paths provided by extensions (FDW/CSP) */ + add_joinrel_extra_paths(root, joinrel); + /* Find and save the cheapest paths for this joinrel */ set_cheapest(joinrel); diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 58d78e6..797c8b8 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -60,6 +60,8 @@ set_rel_pathlist_hook_type set_rel_pathlist_hook = NULL; /* Hook for plugins to replace standard_join_search() */ join_search_hook_type join_search_hook = NULL; +/* Hook for plugins to add extra joinpath for each level */ +set_extra_joinpaths_hook_type set_extra_joinpaths_hook = NULL; static void set_base_rel_sizes(PlannerInfo *root); static void set_base_rel_pathlists(PlannerInfo *root); @@ -1572,6 +1574,293 @@ make_rel_from_joinlist(PlannerInfo *root, List *joinlist) } /* + * lookup_reloptinfo + * + * A utility function to look up a RelOptInfo that represents a set of + * relation ids specified by relids. + */ +static RelOptInfo * +lookup_reloptinfo(PlannerInfo *root, Relids relids) +{ + RelOptInfo *rel = NULL; + + switch (bms_membership(relids)) + { + case BMS_EMPTY_SET: + /* should not happen */ + break; + case BMS_SINGLETON: + rel = find_base_rel(root, bms_singleton_member(relids)); + break; + case BMS_MULTIPLE: + rel = find_join_rel(root, relids); + break; + } + return rel; +} + +/* + * get_joinrel_broken_down + * + * We intend extensions to call this function to break down the supplied + * 'joinrel' into multiple relations that are involved in this join. + * It returns a list of RelOptInfo (which is either RELOPT_BASEREL or + * RELOPT_JOINREL) and SpecialJoinInfo if join-type is not JOIN_INNER. + * If not inner join, length of the RelOptInfo list should be 2, expected + * to join them according to the SpecialJoinInfo also returned. + * If inner join, it returns RelOptInfo more than or equal to 2. + */ +List * +get_joinrel_broken_down(PlannerInfo *root, RelOptInfo *joinrel, + SpecialJoinInfo **p_sjinfo) +{ + List *sjinfo_list = NIL; + List *child_relops = NIL; + Bitmapset *inner_relids = bms_copy(joinrel->relids); + Bitmapset *tempset; + RelOptInfo *rel; + ListCell *lc; + int relid; + + /* sanity checks */ + Assert(joinrel->reloptkind == RELOPT_JOINREL); + Assert(bms_num_members(joinrel->relids) > 1); + + /* + * Walk on the SpecialJoinInfo that specifies the way to join + * relations that are involved in this joinrel. + */ + foreach (lc, root->join_info_list) + { + SpecialJoinInfo *sjinfo = lfirst(lc); + ListCell *prev; + ListCell *cell; + ListCell *next; + + /* This special-join involves any other relations in-addition + * to the relations joined with this joinrel. + */ + if (!bms_is_subset(sjinfo->min_lefthand, joinrel->relids) || + !bms_is_subset(sjinfo->min_righthand, joinrel->relids)) + continue; + + tempset = bms_union(sjinfo->min_lefthand, + sjinfo->min_righthand); + inner_relids = bms_difference(inner_relids, tempset); + + /* + * Remove SpecialJoinInfo if it dominates / is dominated by + * another one. + */ + for (prev = NULL, cell = list_head(sjinfo_list), + next = (!cell ? NULL : lnext(cell)); + cell != NULL; + prev = cell, cell = next, next = (!cell ? NULL : lnext(cell))) + { + SpecialJoinInfo *other = lfirst(cell); + + /* + * If sjinfo is dominated by other one already in the list, + * no need to track this SpecialJoinInfo any more. + */ + if (bms_is_subset(tempset, other->min_lefthand) || + bms_is_subset(tempset, other->min_righthand)) + break; + + /* + * On the other hand, this sjinfo may dominates other one + * already in the list. If so, we don't need to care about + * the older one any more. + */ + if ((bms_is_subset(other->min_lefthand, sjinfo->min_lefthand) && + bms_is_subset(other->min_righthand, sjinfo->min_lefthand)) || + (bms_is_subset(other->min_lefthand, sjinfo->min_righthand) && + bms_is_subset(other->min_righthand, sjinfo->min_righthand))) + { + sjinfo_list = list_delete_cell(sjinfo_list, cell, prev); + cell = prev; + } + } + /* OK, it makes sense to track this sjinfo */ + if (!cell) + sjinfo_list = lappend(sjinfo_list, sjinfo); + } + + /* + * An empty inner_relids means that we have no relations to be + * joined using inner join manner, thus, all we can do is to pick + * up a special join info dominated by this 'joinrel', and split + * it into two portions. + * These two relations shall be able to joined according to the + * SpecialJoinInfo to be returned to the caller. + */ + if (bms_is_empty(inner_relids)) + { + foreach (lc, sjinfo_list) + { + SpecialJoinInfo *sjinfo = lfirst(lc); + RelOptInfo *l_rel; + RelOptInfo *r_rel; + + tempset = bms_difference(joinrel->relids, sjinfo->min_lefthand); + l_rel = lookup_reloptinfo(root, sjinfo->min_lefthand); + r_rel = lookup_reloptinfo(root, tempset); + bms_free(tempset); + + if (l_rel && r_rel) + { + *p_sjinfo = sjinfo; + return list_make2(l_rel, r_rel); + } + + tempset = bms_difference(joinrel->relids, sjinfo->min_righthand); + l_rel = lookup_reloptinfo(root, tempset); + r_rel = lookup_reloptinfo(root, sjinfo->min_righthand); + bms_free(tempset); + + if (l_rel && r_rel) + { + *p_sjinfo = sjinfo; + return list_make2(l_rel, r_rel); + } + } + elog(ERROR, "could not find suitable child joinrels"); + } + + /* + * Elsewhere, all the relations still in 'inner_reilds' shall be + * able to be joined using inner-join manner. + */ + tempset = bms_difference(joinrel->relids, inner_relids); + if (!bms_is_empty(tempset)) + { + rel = lookup_reloptinfo(root, tempset); + if (!rel) + elog(ERROR, "could not find RelOptInfo for given relids"); + child_relops = lappend(child_relops, rel); + } + relid = -1; + while ((relid = bms_next_member(inner_relids, relid)) >= 0) + { + rel = find_base_rel(root, relid); + child_relops = lappend(child_relops, rel); + } + Assert(list_length(child_relops) > 1); + *p_sjinfo = NULL; + return child_relops; +} + +/* + * add_joinrel_extra_paths + * + * Entrypoint of the hooks for FDW/CSP to add alternative scan path + * towards the supplied 'joinrel'. + */ +void +add_joinrel_extra_paths(PlannerInfo *root, RelOptInfo *joinrel) +{ + /* + * Consider the paths added by FDWs if and when all the relations + * involved in this joinrel are managed by same foreign-data wrapper. + * It is role of GetForeignJoinPaths handler of FDW driver to check + * whether the combination of foreign server and/or checkAsUser is + * suitable + */ + if (joinrel->fdwroutine && joinrel->fdwroutine->GetForeignJoinPaths) + joinrel->fdwroutine->GetForeignJoinPaths(root, joinrel); + + /* + * Also, consider paths added by CSPs, not only FDW, if and when + * someone tries to add a custom-path that tries to replace whole + * of the join subtree. (e.g, CSP that inject materialized-view + * scan if join-subtree is strictly matched with its definition) + */ + if (set_extra_joinpaths_hook) + set_extra_joinpaths_hook(root, joinrel); +#if 1 + /* + * The block below is just for observation of the behavior when + * get_joinrel_broken_down() is called back by extensions. + */ + { + SpecialJoinInfo *sjinfo; + List *child_relops; + JoinType jointype; + ListCell *lc; + StringInfoData str; + + child_relops = get_joinrel_broken_down(root, joinrel, &sjinfo); + jointype = (!sjinfo ? JOIN_INNER : sjinfo->jointype); + + initStringInfo(&str); + switch (jointype) + { + case JOIN_INNER: + appendStringInfo(&str, "INNER JOIN: "); + break; + case JOIN_LEFT: + appendStringInfo(&str, "LEFT JOIN: "); + break; + case JOIN_FULL: + appendStringInfo(&str, "FULL JOIN: "); + break; + case JOIN_RIGHT: + appendStringInfo(&str, "RIGHT JOIN: "); + break; + case JOIN_SEMI: + appendStringInfo(&str, "SEMI JOIN: "); + break; + case JOIN_ANTI: + appendStringInfo(&str, "ANTI JOIN: "); + break; + case JOIN_UNIQUE_OUTER: + appendStringInfo(&str, "JOIN UNIQUE OUTER: "); + break; + case JOIN_UNIQUE_INNER: + appendStringInfo(&str, "JOIN UNIQUE INNER: "); + break; + default: + appendStringInfo(&str, "JOIN UNKNOWN: "); + break; + } + + foreach (lc, child_relops) + { + RelOptInfo *rel = lfirst(lc); + RangeTblEntry *rte; + + if (lc != list_head(child_relops)) + appendStringInfo(&str, ", "); + + if (rel->reloptkind == RELOPT_BASEREL) + { + rte = root->simple_rte_array[rel->relid]; + appendStringInfo(&str, "%s", rte->eref->aliasname); + } + else + { + int relid = -1; + bool is_first = true; + + appendStringInfo(&str, "("); + while ((relid = bms_next_member(rel->relids, relid)) >= 0) + { + if (!is_first) + appendStringInfo(&str, ", "); + rte = root->simple_rte_array[relid]; + appendStringInfo(&str, "%s", rte->eref->aliasname); + is_first = false; + } + appendStringInfo(&str, ")"); + } + } + elog(INFO, "%s", str.data); + pfree(str.data); + } +#endif +} + +/* * standard_join_search * Find possible joinpaths for a query by successively finding ways * to join component relations into join relations. @@ -1645,6 +1934,9 @@ standard_join_search(PlannerInfo *root, int levels_needed, List *initial_rels) { rel = (RelOptInfo *) lfirst(lc); + /* Add extra paths provided by extensions (FDW/CSP) */ + add_joinrel_extra_paths(root, rel); + /* Find and save the cheapest paths for this rel */ set_cheapest(rel); diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c index 1da953f..61f1a78 100644 --- a/src/backend/optimizer/path/joinpath.c +++ b/src/backend/optimizer/path/joinpath.c @@ -17,10 +17,13 @@ #include #include "executor/executor.h" +#include "foreign/fdwapi.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" +/* Hook for plugins to get control in add_paths_to_joinrel() */ +set_join_pathlist_hook_type set_join_pathlist_hook = NULL; #define PATH_PARAM_BY_REL(path, rel) \ ((path)->param_info && bms_overlap(PATH_REQ_OUTER(path), (rel)->relids)) @@ -260,6 +263,16 @@ add_paths_to_joinrel(PlannerInfo *root, restrictlist, jointype, sjinfo, &semifactors, param_source_rels, extra_lateral_rels); + + /* + * 5. Consider paths added by custom-scan providers, or other extensions + * in addition to the built-in paths. + */ + if (set_join_pathlist_hook) + set_join_pathlist_hook(root, joinrel, outerrel, innerrel, + restrictlist, jointype, + sjinfo, &semifactors, + param_source_rels, extra_lateral_rels); } /* diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index cb69c03..7f86fcb 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -44,7 +44,6 @@ #include "utils/lsyscache.h" -static Plan *create_plan_recurse(PlannerInfo *root, Path *best_path); static Plan *create_scan_plan(PlannerInfo *root, Path *best_path); static List *build_path_tlist(PlannerInfo *root, Path *path); static bool use_physical_tlist(PlannerInfo *root, RelOptInfo *rel); @@ -220,7 +219,7 @@ create_plan(PlannerInfo *root, Path *best_path) * create_plan_recurse * Recursive guts of create_plan(). */ -static Plan * +Plan * create_plan_recurse(PlannerInfo *root, Path *best_path) { Plan *plan; @@ -1961,16 +1960,26 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path, ForeignScan *scan_plan; RelOptInfo *rel = best_path->path.parent; Index scan_relid = rel->relid; - RangeTblEntry *rte; + Oid rel_oid = InvalidOid; Bitmapset *attrs_used = NULL; ListCell *lc; int i; - /* it should be a base rel... */ - Assert(scan_relid > 0); - Assert(rel->rtekind == RTE_RELATION); - rte = planner_rt_fetch(scan_relid, root); - Assert(rte->rtekind == RTE_RELATION); + /* + * Fetch relation-id, if this foreign-scan node actuall scans on + * a particular real relation. Elsewhere, InvalidOid shall be + * informed to the FDW driver. + */ + if (scan_relid > 0) + { + RangeTblEntry *rte; + + Assert(rel->rtekind == RTE_RELATION); + rte = planner_rt_fetch(scan_relid, root); + Assert(rte->rtekind == RTE_RELATION); + rel_oid = rte->relid; + } + Assert(rel->fdwroutine != NULL); /* * Sort clauses into best execution order. We do this first since the FDW @@ -1985,13 +1994,37 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path, * has selected some join clauses for remote use but also wants them * rechecked locally). */ - scan_plan = rel->fdwroutine->GetForeignPlan(root, rel, rte->relid, + scan_plan = rel->fdwroutine->GetForeignPlan(root, rel, rel_oid, best_path, tlist, scan_clauses); + /* + * Sanity check. Pseudo scan tuple-descriptor shall be constructed + * based on the fdw_ps_tlist, excluding resjunk=true, so we need to + * ensure all valid TLEs have to locate prior to junk ones. + */ + if (scan_plan->scan.scanrelid == 0) + { + bool found_resjunk = false; + + foreach (lc, scan_plan->fdw_ps_tlist) + { + TargetEntry *tle = lfirst(lc); + + if (tle->resjunk) + found_resjunk = true; + else if (found_resjunk) + elog(ERROR, "junk TLE should not apper prior to valid one"); + } + } + /* Set the relids that are represented by this foreign scan for Explain */ + scan_plan->fdw_relids = best_path->path.parent->relids; /* Copy cost data from Path to Plan; no need to make FDW do this */ copy_path_costsize(&scan_plan->scan.plan, &best_path->path); + /* Track FDW server-id; no need to make FDW do this */ + scan_plan->fdw_handler = rel->fdw_handler; + /* * Replace any outer-relation variables with nestloop params in the qual * and fdw_exprs expressions. We do this last so that the FDW doesn't @@ -2053,12 +2086,7 @@ create_customscan_plan(PlannerInfo *root, CustomPath *best_path, { CustomScan *cplan; RelOptInfo *rel = best_path->path.parent; - - /* - * Right now, all we can support is CustomScan node which is associated - * with a particular base relation to be scanned. - */ - Assert(rel && rel->reloptkind == RELOPT_BASEREL); + ListCell *lc; /* * Sort clauses into the best execution order, although custom-scan @@ -2078,6 +2106,28 @@ create_customscan_plan(PlannerInfo *root, CustomPath *best_path, Assert(IsA(cplan, CustomScan)); /* + * Sanity check. Pseudo scan tuple-descriptor shall be constructed + * based on the custom_ps_tlist, excluding resjunk=true, so we need + * to ensure all valid TLEs have to locate prior to junk ones. + */ + if (cplan->scan.scanrelid == 0) + { + bool found_resjunk = false; + + foreach (lc, cplan->custom_ps_tlist) + { + TargetEntry *tle = lfirst(lc); + + if (tle->resjunk) + found_resjunk = true; + else if (found_resjunk) + elog(ERROR, "junk TLE should not apper prior to valid one"); + } + } + /* Set the relids that are represented by this custom scan for Explain */ + cplan->custom_relids = best_path->path.parent->relids; + + /* * Copy cost data from Path to Plan; no need to make custom-plan providers * do this */ diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index ec828cd..2961f44 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -568,6 +568,38 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) { ForeignScan *splan = (ForeignScan *) plan; + if (rtoffset > 0) + splan->fdw_relids = + bms_shift_members(splan->fdw_relids, rtoffset); + + if (splan->scan.scanrelid == 0) + { + indexed_tlist *pscan_itlist = + build_tlist_index(splan->fdw_ps_tlist); + + splan->scan.plan.targetlist = (List *) + fix_upper_expr(root, + (Node *) splan->scan.plan.targetlist, + pscan_itlist, + INDEX_VAR, + rtoffset); + splan->scan.plan.qual = (List *) + fix_upper_expr(root, + (Node *) splan->scan.plan.qual, + pscan_itlist, + INDEX_VAR, + rtoffset); + splan->fdw_exprs = (List *) + fix_upper_expr(root, + (Node *) splan->fdw_exprs, + pscan_itlist, + INDEX_VAR, + rtoffset); + splan->fdw_ps_tlist = + fix_scan_list(root, splan->fdw_ps_tlist, rtoffset); + pfree(pscan_itlist); + break; + } splan->scan.scanrelid += rtoffset; splan->scan.plan.targetlist = fix_scan_list(root, splan->scan.plan.targetlist, rtoffset); @@ -582,6 +614,38 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) { CustomScan *splan = (CustomScan *) plan; + if (rtoffset > 0) + splan->custom_relids = + bms_shift_members(splan->custom_relids, rtoffset); + + if (splan->scan.scanrelid == 0) + { + indexed_tlist *pscan_itlist = + build_tlist_index(splan->custom_ps_tlist); + + splan->scan.plan.targetlist = (List *) + fix_upper_expr(root, + (Node *) splan->scan.plan.targetlist, + pscan_itlist, + INDEX_VAR, + rtoffset); + splan->scan.plan.qual = (List *) + fix_upper_expr(root, + (Node *) splan->scan.plan.qual, + pscan_itlist, + INDEX_VAR, + rtoffset); + splan->custom_exprs = (List *) + fix_upper_expr(root, + (Node *) splan->custom_exprs, + pscan_itlist, + INDEX_VAR, + rtoffset); + splan->custom_ps_tlist = + fix_scan_list(root, splan->custom_ps_tlist, rtoffset); + pfree(pscan_itlist); + break; + } splan->scan.scanrelid += rtoffset; splan->scan.plan.targetlist = fix_scan_list(root, splan->scan.plan.targetlist, rtoffset); diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 313a5c1..1c570c8 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -378,10 +378,15 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, /* Grab the fdwroutine info using the relcache, while we have it */ if (relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE) + { + rel->fdw_handler = GetFdwHandlerForRelation(relation); rel->fdwroutine = GetFdwRoutineForRelation(relation, true); + } else + { + rel->fdw_handler = InvalidOid; rel->fdwroutine = NULL; - + } heap_close(relation, NoLock); /* diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 8cfbea0..5623566 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -14,6 +14,7 @@ */ #include "postgres.h" +#include "foreign/fdwapi.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" @@ -122,6 +123,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind) rel->subroot = NULL; rel->subplan_params = NIL; rel->fdwroutine = NULL; + rel->fdw_handler = InvalidOid; rel->fdw_private = NULL; rel->baserestrictinfo = NIL; rel->baserestrictcost.startup = 0; @@ -427,6 +429,18 @@ build_join_rel(PlannerInfo *root, sjinfo, restrictlist); /* + * Set FDW handler and routine if both outer and inner relation + * are managed by same FDW driver. + */ + if (OidIsValid(outer_rel->fdw_handler) && + OidIsValid(inner_rel->fdw_handler) && + outer_rel->fdw_handler == inner_rel->fdw_handler) + { + joinrel->fdw_handler = outer_rel->fdw_handler; + joinrel->fdwroutine = GetFdwRoutine(joinrel->fdw_handler); + } + + /* * Add the joinrel to the query's joinrel list, and store it into the * auxiliary hashtable if there is one. NB: GEQO requires us to append * the new joinrel to the end of the list! diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 28e1acf..90e1107 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -3842,6 +3842,10 @@ set_deparse_planstate(deparse_namespace *dpns, PlanState *ps) /* index_tlist is set only if it's an IndexOnlyScan */ if (IsA(ps->plan, IndexOnlyScan)) dpns->index_tlist = ((IndexOnlyScan *) ps->plan)->indextlist; + else if (IsA(ps->plan, ForeignScan)) + dpns->index_tlist = ((ForeignScan *) ps->plan)->fdw_ps_tlist; + else if (IsA(ps->plan, CustomScan)) + dpns->index_tlist = ((CustomScan *) ps->plan)->custom_ps_tlist; else dpns->index_tlist = NIL; } diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h index 1d76841..ab0a05d 100644 --- a/src/include/foreign/fdwapi.h +++ b/src/include/foreign/fdwapi.h @@ -82,6 +82,9 @@ typedef void (*EndForeignModify_function) (EState *estate, typedef int (*IsForeignRelUpdatable_function) (Relation rel); +typedef void (*GetForeignJoinPaths_function ) (PlannerInfo *root, + RelOptInfo *joinrel); + typedef void (*ExplainForeignScan_function) (ForeignScanState *node, struct ExplainState *es); @@ -150,6 +153,10 @@ typedef struct FdwRoutine /* Support functions for IMPORT FOREIGN SCHEMA */ ImportForeignSchema_function ImportForeignSchema; + + /* Support functions for join push-down */ + GetForeignJoinPaths_function GetForeignJoinPaths; + } FdwRoutine; @@ -157,6 +164,7 @@ typedef struct FdwRoutine extern FdwRoutine *GetFdwRoutine(Oid fdwhandler); extern FdwRoutine *GetFdwRoutineByRelId(Oid relid); extern FdwRoutine *GetFdwRoutineForRelation(Relation relation, bool makecopy); +extern Oid GetFdwHandlerForRelation(Relation relation); extern bool IsImportableForeignTable(const char *tablename, ImportForeignSchemaStmt *stmt); diff --git a/src/include/nodes/bitmapset.h b/src/include/nodes/bitmapset.h index 3a556ee..3ca9791 100644 --- a/src/include/nodes/bitmapset.h +++ b/src/include/nodes/bitmapset.h @@ -66,6 +66,7 @@ extern void bms_free(Bitmapset *a); extern Bitmapset *bms_union(const Bitmapset *a, const Bitmapset *b); extern Bitmapset *bms_intersect(const Bitmapset *a, const Bitmapset *b); extern Bitmapset *bms_difference(const Bitmapset *a, const Bitmapset *b); +extern Bitmapset *bms_shift_members(const Bitmapset *a, int shift); extern bool bms_is_subset(const Bitmapset *a, const Bitmapset *b); extern BMS_Comparison bms_subset_compare(const Bitmapset *a, const Bitmapset *b); extern bool bms_is_member(int x, const Bitmapset *a); diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 21cbfa8..b25330e 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -471,7 +471,13 @@ typedef struct WorkTableScan * fdw_exprs and fdw_private are both under the control of the foreign-data * wrapper, but fdw_exprs is presumed to contain expression trees and will * be post-processed accordingly by the planner; fdw_private won't be. - * Note that everything in both lists must be copiable by copyObject(). + * An optional fdw_ps_tlist is used to map a reference to an attribute of + * underlying relation(s) on a pair of INDEX_VAR and alternative varattno. + * It looks like a scan on pseudo relation that is usually result of + * relations join on remote data source, and FDW driver is responsible to + * set expected target list for this. If FDW returns records as foreign- + * table definition, just put NIL here. + * Note that everything in above lists must be copiable by copyObject(). * One way to store an arbitrary blob of bytes is to represent it as a bytea * Const. Usually, though, you'll be better off choosing a representation * that can be dumped usefully by nodeToString(). @@ -480,18 +486,23 @@ typedef struct WorkTableScan typedef struct ForeignScan { Scan scan; + Oid fdw_handler; /* OID of FDW handler */ List *fdw_exprs; /* expressions that FDW may evaluate */ + List *fdw_ps_tlist; /* optional pseudo-scan tlist for FDW */ List *fdw_private; /* private data for FDW */ + Bitmapset *fdw_relids; /* set of relid (index of range-tables) + * represented by this node */ bool fsSystemCol; /* true if any "system column" is needed */ } ForeignScan; /* ---------------- * CustomScan node * - * The comments for ForeignScan's fdw_exprs and fdw_private fields apply - * equally to custom_exprs and custom_private. Note that since Plan trees - * can be copied, custom scan providers *must* fit all plan data they need - * into those fields; embedding CustomScan in a larger struct will not work. + * The comments for ForeignScan's fdw_exprs, fdw_varmap and fdw_private fields + * apply equally to custom_exprs, custom_ps_tlist and custom_private. + * Note that since Plan trees can be copied, custom scan providers *must* + * fit all plan data they need into those fields; embedding CustomScan in + * a larger struct will not work. * ---------------- */ struct CustomScan; @@ -512,7 +523,10 @@ typedef struct CustomScan Scan scan; uint32 flags; /* mask of CUSTOMPATH_* flags, see relation.h */ List *custom_exprs; /* expressions that custom code may evaluate */ + List *custom_ps_tlist;/* optional pseudo-scan target list */ List *custom_private; /* private data for custom code */ + Bitmapset *custom_relids; /* set of relid (index of range-tables) + * represented by this node */ const CustomScanMethods *methods; } CustomScan; diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 334cf51..4eb89c6 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -366,6 +366,7 @@ typedef struct PlannerInfo * subroot - PlannerInfo for subquery (NULL if it's not a subquery) * subplan_params - list of PlannerParamItems to be passed to subquery * fdwroutine - function hooks for FDW, if foreign table (else NULL) + * fdw_handler - OID of FDW handler, if foreign table (else InvalidOid) * fdw_private - private state for FDW, if foreign table (else NULL) * * Note: for a subquery, tuples, subplan, subroot are not set immediately @@ -461,6 +462,7 @@ typedef struct RelOptInfo List *subplan_params; /* if subquery */ /* use "struct FdwRoutine" to avoid including fdwapi.h here */ struct FdwRoutine *fdwroutine; /* if foreign table */ + Oid fdw_handler; /* if foreign table */ void *fdw_private; /* if foreign table */ /* used by various scans and joins: */ diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index 6cad92e..e5676c8 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -30,13 +30,34 @@ typedef void (*set_rel_pathlist_hook_type) (PlannerInfo *root, RangeTblEntry *rte); extern PGDLLIMPORT set_rel_pathlist_hook_type set_rel_pathlist_hook; +/* Hook for plugins to get control in add_paths_to_joinrel() */ +typedef void (*set_join_pathlist_hook_type) (PlannerInfo *root, + RelOptInfo *joinrel, + RelOptInfo *outerrel, + RelOptInfo *innerrel, + List *restrictlist, + JoinType jointype, + SpecialJoinInfo *sjinfo, + SemiAntiJoinFactors *semifactors, + Relids param_source_rels, + Relids extra_lateral_rels); +extern PGDLLIMPORT set_join_pathlist_hook_type set_join_pathlist_hook; + /* Hook for plugins to replace standard_join_search() */ typedef RelOptInfo *(*join_search_hook_type) (PlannerInfo *root, int levels_needed, List *initial_rels); extern PGDLLIMPORT join_search_hook_type join_search_hook; +/* Hook for plugins to add extra joinpath for each level */ +typedef void (*set_extra_joinpaths_hook_type)(PlannerInfo *root, + RelOptInfo *joinrel); +extern PGDLLIMPORT set_extra_joinpaths_hook_type set_extra_joinpaths_hook; +extern List *get_joinrel_broken_down(PlannerInfo *root, + RelOptInfo *joinrel, + SpecialJoinInfo **p_sjinfo); +extern void add_joinrel_extra_paths(PlannerInfo *root, RelOptInfo *joinrel); extern RelOptInfo *make_one_rel(PlannerInfo *root, List *joinlist); extern RelOptInfo *standard_join_search(PlannerInfo *root, int levels_needed, List *initial_rels); diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h index fa72918..0c8cbcd 100644 --- a/src/include/optimizer/planmain.h +++ b/src/include/optimizer/planmain.h @@ -41,6 +41,7 @@ extern Plan *optimize_minmax_aggregates(PlannerInfo *root, List *tlist, * prototypes for plan/createplan.c */ extern Plan *create_plan(PlannerInfo *root, Path *best_path); +extern Plan *create_plan_recurse(PlannerInfo *root, Path *best_path); extern SubqueryScan *make_subqueryscan(List *qptlist, List *qpqual, Index scanrelid, Plan *subplan); extern ForeignScan *make_foreignscan(List *qptlist, List *qpqual,