doc/src/sgml/fdwhandler.sgml | 26 ++++++++++++++++++++++++- src/backend/commands/explain.c | 23 ++++++++++++++++++++++ src/backend/executor/execScan.c | 34 +++++++++++++++++++++++++++++---- src/backend/executor/nodeForeignscan.c | 13 +++++++++++++ src/backend/nodes/copyfuncs.c | 1 + src/backend/nodes/nodeFuncs.c | 7 +++++++ src/backend/nodes/outfuncs.c | 2 ++ src/backend/nodes/readfuncs.c | 1 + src/backend/optimizer/plan/createplan.c | 13 ++++++++++++- src/backend/optimizer/plan/setrefs.c | 14 ++++++++++++++ src/backend/optimizer/plan/subselect.c | 11 +++++++++++ src/include/foreign/fdwapi.h | 7 ++++++- src/include/nodes/execnodes.h | 1 + src/include/nodes/plannodes.h | 1 + src/include/nodes/relation.h | 1 + 15 files changed, 148 insertions(+), 7 deletions(-) diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml index 1533a6b..13bfad9 100644 --- a/doc/src/sgml/fdwhandler.sgml +++ b/doc/src/sgml/fdwhandler.sgml @@ -168,7 +168,8 @@ GetForeignPlan (PlannerInfo *root, Oid foreigntableid, ForeignPath *best_path, List *tlist, - List *scan_clauses); + List *scan_clauses, + List *fdw_plans) Create a ForeignScan plan node from the selected foreign @@ -259,6 +260,29 @@ IterateForeignScan (ForeignScanState *node); +bool +RecheckForeignScan (ForeignScanState *node, TupleTableSlot *slot); + + Rechecks visibility of the EPQ tuples according to the qualifiers + pushed-down. + This callback is optional, if this ForeignScanState + runs on a base foreign table. fdw_recheck_quals + can be used instead to recheck on the target EPQ tuple by the backend. + + + On the other hands, if scanrelid equals zero thus it + represents a join sub-tree of foreign tables, this callback is + expected to reconstruct a joined tuple using the primitive EPQ + tuples and fill up the supplied slot according to + the fdw_scan_tlist definition. + Also, this callback can or must recheck scan qualifiers and join + conditions which are pushed down. Especially, it needs special + handling if not simple inner-join, instead of the backend support + by fdw_recheck_quals. + + + + void ReScanForeignScan (ForeignScanState *node); diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 7fb8a14..60522ef 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -114,6 +114,8 @@ static void ExplainMemberNodes(List *plans, PlanState **planstates, List *ancestors, ExplainState *es); static void ExplainSubPlans(List *plans, List *ancestors, const char *relationship, ExplainState *es); +static void ExplainForeignChildren(ForeignScanState *fss, + List *ancestors, ExplainState *es); static void ExplainCustomChildren(CustomScanState *css, List *ancestors, ExplainState *es); static void ExplainProperty(const char *qlabel, const char *value, @@ -1547,6 +1549,8 @@ ExplainNode(PlanState *planstate, List *ancestors, IsA(plan, BitmapAnd) || IsA(plan, BitmapOr) || IsA(plan, SubqueryScan) || + (IsA(planstate, ForeignScanState) && + ((ForeignScanState *) planstate)->fdw_ps != NIL) || (IsA(planstate, CustomScanState) && ((CustomScanState *) planstate)->custom_ps != NIL) || planstate->subPlan; @@ -1603,6 +1607,10 @@ ExplainNode(PlanState *planstate, List *ancestors, ExplainNode(((SubqueryScanState *) planstate)->subplan, ancestors, "Subquery", NULL, es); break; + case T_ForeignScan: + ExplainForeignChildren((ForeignScanState *) planstate, + ancestors, es); + break; case T_CustomScan: ExplainCustomChildren((CustomScanState *) planstate, ancestors, es); @@ -2643,6 +2651,21 @@ ExplainSubPlans(List *plans, List *ancestors, } /* + * Explain a list of children of a ForeignScan. + */ +static void +ExplainForeignChildren(ForeignScanState *fss, + List *ancestors, ExplainState *es) +{ + ListCell *cell; + const char *label = + (list_length(fss->fdw_ps) != 1 ? "children" : "child"); + + foreach(cell, fss->fdw_ps) + ExplainNode((PlanState *) lfirst(cell), ancestors, label, NULL, es); +} + +/* * Explain a list of children of a CustomScan. */ static void diff --git a/src/backend/executor/execScan.c b/src/backend/executor/execScan.c index a96e826..b472bf7 100644 --- a/src/backend/executor/execScan.c +++ b/src/backend/executor/execScan.c @@ -49,8 +49,16 @@ ExecScanFetch(ScanState *node, */ Index scanrelid = ((Scan *) node->ps.plan)->scanrelid; - Assert(scanrelid > 0); - if (estate->es_epqTupleSet[scanrelid - 1]) + if (scanrelid == 0) + { + TupleTableSlot *slot = node->ss_ScanTupleSlot; + + /* Check if it meets the access-method conditions */ + if (!(*recheckMtd) (node, slot)) + ExecClearTuple(slot); /* would not be returned by scan */ + return slot; + } + else if (estate->es_epqTupleSet[scanrelid - 1]) { TupleTableSlot *slot = node->ss_ScanTupleSlot; @@ -347,8 +355,26 @@ ExecScanReScan(ScanState *node) { Index scanrelid = ((Scan *) node->ps.plan)->scanrelid; - Assert(scanrelid > 0); + if (scanrelid > 0) + estate->es_epqScanDone[scanrelid - 1] = false; + else + { + Bitmapset *relids; + int rtindex = -1; + + if (IsA(node->ps.plan, ForeignScan)) + relids = ((ForeignScan *) node->ps.plan)->fs_relids; + else if (IsA(node->ps.plan, CustomScan)) + relids = ((CustomScan *) node->ps.plan)->custom_relids; + else + elog(ERROR, "unexpected scan node: %d", + (int)nodeTag(node->ps.plan)); - estate->es_epqScanDone[scanrelid - 1] = false; + while ((rtindex = bms_next_member(relids, rtindex)) >= 0) + { + Assert(rtindex > 0); + estate->es_epqScanDone[rtindex - 1] = false; + } + } } } diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c index 6165e4a..1344c32 100644 --- a/src/backend/executor/nodeForeignscan.c +++ b/src/backend/executor/nodeForeignscan.c @@ -73,6 +73,7 @@ ForeignNext(ForeignScanState *node) static bool ForeignRecheck(ForeignScanState *node, TupleTableSlot *slot) { + FdwRoutine *fdwroutine = node->fdwroutine; ExprContext *econtext; /* @@ -85,6 +86,18 @@ ForeignRecheck(ForeignScanState *node, TupleTableSlot *slot) ResetExprContext(econtext); + /* + * FDW driver has to recheck visibility of EPQ tuple towards + * the scan qualifiers once it gets pushed down. + * In addition, if this node represents a join sub-tree, not + * a scan, FDW driver is also responsible to reconstruct + * a joined tuple according to the primitive EPQ tuples. + */ + if (fdwroutine->RecheckForeignScan) + { + if (!fdwroutine->RecheckForeignScan(node, slot)) + return false; + } return ExecQual(node->fdw_recheck_quals, econtext, false); } diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index c176ff9..21df5ce 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -645,6 +645,7 @@ _copyForeignScan(const ForeignScan *from) * copy remainder of node */ COPY_SCALAR_FIELD(fs_server); + COPY_NODE_FIELD(fdw_plans); COPY_NODE_FIELD(fdw_exprs); COPY_NODE_FIELD(fdw_private); COPY_NODE_FIELD(fdw_scan_tlist); diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index a11cb9f..99e03a9 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -3485,6 +3485,13 @@ planstate_tree_walker(PlanState *planstate, bool (*walker) (), void *context) if (walker(((SubqueryScanState *) planstate)->subplan, context)) return true; break; + case T_ForeignScan: + foreach (lc, ((ForeignScanState *) planstate)->fdw_ps) + { + if (walker((PlanState *) lfirst(lc), context)) + return true; + } + break; case T_CustomScan: foreach (lc, ((CustomScanState *) planstate)->custom_ps) { diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 3e75cd1..fafd6b3 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -591,6 +591,7 @@ _outForeignScan(StringInfo str, const ForeignScan *node) _outScanInfo(str, (const Scan *) node); WRITE_OID_FIELD(fs_server); + WRITE_NODE_FIELD(fdw_plans); WRITE_NODE_FIELD(fdw_exprs); WRITE_NODE_FIELD(fdw_private); WRITE_NODE_FIELD(fdw_scan_tlist); @@ -1680,6 +1681,7 @@ _outForeignPath(StringInfo str, const ForeignPath *node) _outPathInfo(str, (const Path *) node); + WRITE_NODE_FIELD(fdw_paths); WRITE_NODE_FIELD(fdw_private); } diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 94ba6dc..4b54016 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -1795,6 +1795,7 @@ _readForeignScan(void) ReadCommonScan(&local_node->scan); READ_OID_FIELD(fs_server); + READ_NODE_FIELD(fdw_plans); READ_NODE_FIELD(fdw_exprs); READ_NODE_FIELD(fdw_private); READ_NODE_FIELD(fdw_scan_tlist); diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 791b64e..9dc445e 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -2095,11 +2095,20 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path, Index scan_relid = rel->relid; Oid rel_oid = InvalidOid; Bitmapset *attrs_used = NULL; + List *fdw_plans = NIL; ListCell *lc; int i; Assert(rel->fdwroutine != NULL); + /* Recursively transform child paths. */ + foreach (lc, best_path->fdw_paths) + { + Plan *plan = create_plan_recurse(root, (Path *) lfirst(lc)); + + fdw_plans = lappend(fdw_plans, plan); + } + /* * If we're scanning a base relation, fetch its OID. (Irrelevant if * scanning a join relation.) @@ -2129,7 +2138,9 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path, */ scan_plan = rel->fdwroutine->GetForeignPlan(root, rel, rel_oid, best_path, - tlist, scan_clauses); + tlist, + scan_clauses, + fdw_plans); /* Copy cost data from Path to Plan; no need to make FDW do this */ copy_path_costsize(&scan_plan->scan.plan, &best_path->path); diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 48d6e6f..7e4d092 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -1102,6 +1102,8 @@ set_foreignscan_references(PlannerInfo *root, ForeignScan *fscan, int rtoffset) { + ListCell *lc; + /* Adjust scanrelid if it's valid */ if (fscan->scan.scanrelid > 0) fscan->scan.scanrelid += rtoffset; @@ -1129,6 +1131,12 @@ set_foreignscan_references(PlannerInfo *root, itlist, INDEX_VAR, rtoffset); + fscan->fdw_recheck_quals = (List *) + fix_upper_expr(root, + (Node *) fscan->fdw_recheck_quals, + itlist, + INDEX_VAR, + rtoffset); pfree(itlist); /* fdw_scan_tlist itself just needs fix_scan_list() adjustments */ fscan->fdw_scan_tlist = @@ -1147,6 +1155,12 @@ set_foreignscan_references(PlannerInfo *root, fix_scan_list(root, fscan->fdw_recheck_quals, rtoffset); } + /* Adjust child plan-nodes recursively, if needed */ + foreach (lc, fscan->fdw_plans) + { + lfirst(lc) = set_plan_refs(root, (Plan *) lfirst(lc), rtoffset); + } + /* Adjust fs_relids if needed */ if (rtoffset > 0) { diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c index 82414d4..7b50455 100644 --- a/src/backend/optimizer/plan/subselect.c +++ b/src/backend/optimizer/plan/subselect.c @@ -2396,6 +2396,7 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params, case T_ForeignScan: { ForeignScan *fscan = (ForeignScan *) plan; + ListCell *lc; finalize_primnode((Node *) fscan->fdw_exprs, &context); @@ -2405,6 +2406,16 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params, /* We assume fdw_scan_tlist cannot contain Params */ context.paramids = bms_add_members(context.paramids, scan_params); + /* child nodes if any */ + foreach (lc, fscan->fdw_plans) + { + context.paramids = + bms_add_members(context.paramids, + finalize_plan(root, + (Plan *) lfirst(lc), + valid_params, + scan_params)); + } } break; diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h index 69b48b4..4a41351 100644 --- a/src/include/foreign/fdwapi.h +++ b/src/include/foreign/fdwapi.h @@ -36,13 +36,17 @@ typedef ForeignScan *(*GetForeignPlan_function) (PlannerInfo *root, Oid foreigntableid, ForeignPath *best_path, List *tlist, - List *scan_clauses); + List *scan_clauses, + List *fdw_plans); typedef void (*BeginForeignScan_function) (ForeignScanState *node, int eflags); typedef TupleTableSlot *(*IterateForeignScan_function) (ForeignScanState *node); +typedef bool (*RecheckForeignScan_function) (ForeignScanState *node, + TupleTableSlot *slot); + typedef void (*ReScanForeignScan_function) (ForeignScanState *node); typedef void (*EndForeignScan_function) (ForeignScanState *node); @@ -138,6 +142,7 @@ typedef struct FdwRoutine GetForeignPlan_function GetForeignPlan; BeginForeignScan_function BeginForeignScan; IterateForeignScan_function IterateForeignScan; + RecheckForeignScan_function RecheckForeignScan; ReScanForeignScan_function ReScanForeignScan; EndForeignScan_function EndForeignScan; diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 58ec889..c5c89de 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -1582,6 +1582,7 @@ typedef struct ForeignScanState List *fdw_recheck_quals; /* original quals not in ss.ps.qual */ /* use struct pointer to avoid including fdwapi.h here */ struct FdwRoutine *fdwroutine; + List *fdw_ps; /* list of child PlanState nodes, if any */ void *fdw_state; /* foreign-data wrapper can keep state here */ } ForeignScanState; diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 6b28c8e..bd73371 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -526,6 +526,7 @@ typedef struct ForeignScan { Scan scan; Oid fs_server; /* OID of foreign server */ + List *fdw_plans; /* list of Plan nodes, if any */ List *fdw_exprs; /* expressions that FDW may evaluate */ List *fdw_private; /* private data for FDW */ List *fdw_scan_tlist; /* optional tlist describing scan tuple */ diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 6cf2e24..707927c 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -907,6 +907,7 @@ typedef struct TidPath typedef struct ForeignPath { Path path; + List *fdw_paths; List *fdw_private; } ForeignPath;