diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index 4c49776..91a98fa 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -287,6 +287,15 @@ static bool postgresAnalyzeForeignTable(Relation relation, BlockNumber *totalpages); static List *postgresImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid); +static void postgresGetForeignJoinPath(PlannerInfo *root, + RelOptInfo *joinrel, + RelOptInfo *outerrel, + RelOptInfo *innerrel, + JoinType jointype, + SpecialJoinInfo *sjinfo, + SemiAntiJoinFactors *semifactors, + List *restrictlisti, + Relids extra_lateral_rels); /* * Helper functions @@ -367,6 +376,13 @@ postgres_fdw_handler(PG_FUNCTION_ARGS) /* Support functions for IMPORT FOREIGN SCHEMA */ routine->ImportForeignSchema = postgresImportForeignSchema; + /* Support functions for join push-down */ + routine->GetForeignJoinPath = postgresGetForeignJoinPath; + routine->BeginForeignJoin = NULL; + routine->ReScanForeignJoin = NULL; + routine->IterateForeignJoin = NULL; + routine->EndForeignJoin = NULL; + PG_RETURN_POINTER(routine); } @@ -2832,6 +2848,49 @@ postgresImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid) } /* + * postgresGetForeignJoinPath + * Add possible ForeignJoinPath to joinrel. + * + */ +static void +postgresGetForeignJoinPath(PlannerInfo *root, + RelOptInfo *joinrel, + RelOptInfo *outerrel, + RelOptInfo *innerrel, + JoinType jointype, + SpecialJoinInfo *sjinfo, + SemiAntiJoinFactors *semifactors, + List *restrictlist, + Relids extra_lateral_rels) +{ + ListCell *lc; + + foreach(lc, outerrel->pathlist) + { + Path *outerpath = (Path *) lfirst(lc); + Path *innerpath = innerrel->cheapest_total_path; + ForeignJoinPath *joinpath; + Relids required_outer; + + required_outer = calc_non_nestloop_required_outer(outerpath, innerpath); + joinpath = create_foreignjoin_path(root, + joinrel, + jointype, + sjinfo, + semifactors, + outerpath, + innerpath, + restrictlist, + NIL, + required_outer); + + /* TODO generate SQL for the join and store it into fdw_private. */ + /* TODO determine cost and rows of the join. */ + /* TODO add generated path into joinrel by add_path(). */ + } +} + +/* * Create a tuple from the specified row of the PGresult. * * rel is the local representation of the foreign table, attinmeta is diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 781a736..8559e3f 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -900,6 +900,10 @@ ExplainNode(PlanState *planstate, List *ancestors, pname = "Hash"; /* "Join" gets added by jointype switch */ sname = "Hash Join"; break; + case T_ForeignJoin: + pname = "Foreign"; /* "Join" gets added by jointype switch */ + sname = "Foreign Join"; + break; case T_SeqScan: pname = sname = "Seq Scan"; break; @@ -1090,6 +1094,7 @@ ExplainNode(PlanState *planstate, List *ancestors, case T_NestLoop: case T_MergeJoin: case T_HashJoin: + case T_ForeignJoin: { const char *jointype; @@ -1390,6 +1395,18 @@ ExplainNode(PlanState *planstate, List *ancestors, show_instrumentation_count("Rows Removed by Filter", 2, planstate, es); break; + case T_ForeignJoin: + /* TODO: add FDW-specific EXPLAIN information */ + show_upper_qual(((ForeignJoin *) plan)->join.joinqual, + "Join Filter", planstate, ancestors, es); + if (((ForeignJoin *) plan)->join.joinqual) + show_instrumentation_count("Rows Removed by Join Filter", 1, + planstate, es); + show_upper_qual(plan->qual, "Filter", planstate, ancestors, es); + if (plan->qual) + show_instrumentation_count("Rows Removed by Filter", 2, + planstate, es); + break; case T_Agg: show_agg_keys((AggState *) planstate, ancestors, es); show_upper_qual(plan->qual, "Filter", planstate, ancestors, es); diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile index 6081b56..308cc99 100644 --- a/src/backend/executor/Makefile +++ b/src/backend/executor/Makefile @@ -24,6 +24,7 @@ OBJS = execAmi.o execCurrent.o execGrouping.o execJunk.o execMain.o \ nodeSeqscan.o nodeSetOp.o nodeSort.o nodeUnique.o \ nodeValuesscan.o nodeCtescan.o nodeWorktablescan.o \ nodeGroup.o nodeSubplan.o nodeSubqueryscan.o nodeTidscan.o \ - nodeForeignscan.o nodeWindowAgg.o tstoreReceiver.o spi.o + nodeForeignscan.o nodeWindowAgg.o tstoreReceiver.o spi.o \ + nodeForeignjoin.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c index 640964c..6b6d230 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/nodeForeignjoin.h" #include "executor/nodeForeignscan.h" #include "executor/nodeFunctionscan.h" #include "executor/nodeGroup.h" @@ -209,6 +210,10 @@ ExecReScan(PlanState *node) ExecReScanHashJoin((HashJoinState *) node); break; + case T_ForeignJoinState: + ExecReScanForeignJoin((ForeignJoinState *) node); + break; + case T_MaterialState: ExecReScanMaterial((MaterialState *) node); break; diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c index c0189eb..e7d9a6e 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/nodeForeignjoin.h" #include "executor/nodeForeignscan.h" #include "executor/nodeFunctionscan.h" #include "executor/nodeGroup.h" @@ -262,6 +263,11 @@ ExecInitNode(Plan *node, EState *estate, int eflags) estate, eflags); break; + case T_ForeignJoin: + result = (PlanState *) ExecInitForeignJoin((ForeignJoin *) node, + estate, eflags); + break; + /* * materialization nodes */ @@ -457,6 +463,10 @@ ExecProcNode(PlanState *node) result = ExecHashJoin((HashJoinState *) node); break; + case T_ForeignJoinState: + result = ExecForeignJoin((ForeignJoinState *) node); + break; + /* * materialization nodes */ @@ -693,6 +703,10 @@ ExecEndNode(PlanState *node) ExecEndHashJoin((HashJoinState *) node); break; + case T_ForeignJoinState: + ExecEndForeignJoin((ForeignJoinState *) node); + break; + /* * materialization nodes */ diff --git a/src/backend/executor/nodeForeignjoin.c b/src/backend/executor/nodeForeignjoin.c new file mode 100644 index 0000000..fbcdbc3 --- /dev/null +++ b/src/backend/executor/nodeForeignjoin.c @@ -0,0 +1,176 @@ +/*------------------------------------------------------------------------- + * + * nodeForeignjoin.c + * routines to support foreign joins + * + * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/executor/nodeForeignjoin.c + * + *------------------------------------------------------------------------- + */ +/* + * INTERFACE ROUTINES + * ExecForeignJoin - process a foreign join of two foreign plans + * ExecInitForeignJoin - initialize the join + * ExecEndForeignJoin - shut down the join + */ + +#include "postgres.h" + +#include "executor/executor.h" +#include "executor/nodeForeignjoin.h" +#include "foreign/fdwapi.h" +#include "utils/memutils.h" + + +/* ---------------------------------------------------------------- + * ExecForeignJoin(node) + * + * Returns the tuple joined from inner and outer tuples which + * satisfies the qualification clause. + * + * It delegates actual join processing to the foreign data wrapper + * associsated with the foreign tables used in the join subtree. + * Basically this node is very similar to ForeignScan except ForeignJoin + * holds subplans of inner and outer relations. + * + * NULL is returned if all the remaining tuples are consumed. + * + * Basically this funcitons is very similar to ForeignNext called from + * ExecForeignScan. + * + * ---------------------------------------------------------------- + */ +TupleTableSlot * +ExecForeignJoin(ForeignJoinState *node) +{ + TupleTableSlot *slot; + ExprContext *econtext = node->js.ps.ps_ExprContext; + MemoryContext oldcontext; + + /* Call the Iterate function in short-lived context */ + oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); + slot = node->fdwroutine->IterateForeignJoin(node); + MemoryContextSwitchTo(oldcontext); + + /* + * We don't force the slot into the "materialized" state, unlike + * ExecForeignScan, because no system attribute is valid in joined result + * tuple. + */ + return slot; +} + +/* ---------------------------------------------------------------- + * ExecInitForeignJoin + * ---------------------------------------------------------------- + */ +ForeignJoinState * +ExecInitForeignJoin(ForeignJoin *node, EState *estate, int eflags) +{ + ForeignJoinState *fjstate; + FdwRoutine *fdwroutine; + + /* check for unsupported flags */ + Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK))); + + /* + * create state structure + */ + fjstate = makeNode(ForeignJoinState); + fjstate->js.ps.plan = (Plan *) node; + fjstate->js.ps.state = estate; + + /* + * Miscellaneous initialization + * + * create expression context for node + */ + ExecAssignExprContext(estate, &fjstate->js.ps); + + /* + * initialize child expressions + */ + fjstate->js.ps.targetlist = (List *) + ExecInitExpr((Expr *) node->join.plan.targetlist, + (PlanState *) fjstate); + fjstate->js.ps.qual = (List *) + ExecInitExpr((Expr *) node->join.plan.qual, + (PlanState *) fjstate); + fjstate->js.jointype = node->join.jointype; + fjstate->js.joinqual = (List *) + ExecInitExpr((Expr *) node->join.joinqual, + (PlanState *) fjstate); + + /* + * tuple table initialization + */ + ExecInitResultTupleSlot(estate, &fjstate->js.ps); + + /* + * initialize tuple type and projection info + */ + ExecAssignResultTypeFromTL(&fjstate->js.ps); + ExecAssignProjectionInfo(&fjstate->js.ps, NULL); + + /* + * Acquire function pointers from the FDW's handler, and init fdw_state. + */ + fdwroutine = GetFdwRoutineByServerId(node->serverid); + fjstate->fdwroutine = fdwroutine; + fjstate->fdw_state = NULL; + + /* + * Tell the FDW to initiate the join. + */ + fdwroutine->BeginForeignJoin(fjstate, eflags); + + /* + * finally, wipe the current outer tuple clean. + */ + fjstate->js.ps.ps_TupFromTlist = false; + + return fjstate; +} + +/* ---------------------------------------------------------------- + * ExecEndForeignJoin + * + * closes down scans and frees allocated storage + * ---------------------------------------------------------------- + */ +void +ExecEndForeignJoin(ForeignJoinState *node) +{ + /* + * Free the exprcontext + */ + ExecFreeExprContext(&node->js.ps); + + /* + * clean out the tuple table + */ + ExecClearTuple(node->js.ps.ps_ResultTupleSlot); + + /* + * Tell the FDW that the join is done. + */ + node->fdwroutine->EndForeignJoin(node); +} + +/* ---------------------------------------------------------------- + * ExecReScanForeignJoin + * ---------------------------------------------------------------- + */ +void +ExecReScanForeignJoin(ForeignJoinState *node) +{ + /* + * Tell the FDW to rewind the join. + */ + node->fdwroutine->ReScanForeignJoin(node); +} diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c index 4f5f6ae..9fd1069 100644 --- a/src/backend/foreign/foreign.c +++ b/src/backend/foreign/foreign.c @@ -250,6 +250,29 @@ GetForeignTable(Oid relid) /* + * GetForeignTableServerOid - Get OID of the server related to the given + * foreign table. + */ +Oid +GetForeignTableServerOid(Oid relid) +{ + Form_pg_foreign_table tableform; + HeapTuple tp; + Oid serverid; + + tp = SearchSysCache1(FOREIGNTABLEREL, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for foreign table %u", relid); + tableform = (Form_pg_foreign_table) GETSTRUCT(tp); + serverid = tableform->ftserver; + + ReleaseSysCache(tp); + + return serverid; +} + + +/* * GetForeignColumnOptions - Get attfdwoptions of given relation/attnum * as list of DefElem. */ @@ -311,12 +334,8 @@ FdwRoutine * GetFdwRoutineByRelId(Oid relid) { HeapTuple tp; - Form_pg_foreign_data_wrapper fdwform; - Form_pg_foreign_server serverform; Form_pg_foreign_table tableform; Oid serverid; - Oid fdwid; - Oid fdwhandler; /* Get server OID for the foreign table. */ tp = SearchSysCache1(FOREIGNTABLEREL, ObjectIdGetDatum(relid)); @@ -326,6 +345,18 @@ GetFdwRoutineByRelId(Oid relid) serverid = tableform->ftserver; ReleaseSysCache(tp); + return GetFdwRoutineByServerId(serverid); +} + +FdwRoutine * +GetFdwRoutineByServerId(Oid serverid) +{ + HeapTuple tp; + Form_pg_foreign_data_wrapper fdwform; + Form_pg_foreign_server serverform; + Oid fdwid; + Oid fdwhandler; + /* Get foreign-data wrapper OID for the server. */ tp = SearchSysCache1(FOREIGNSERVEROID, ObjectIdGetDatum(serverid)); if (!HeapTupleIsValid(tp)) diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 225756c..c8113ab 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -703,6 +703,27 @@ _copyHashJoin(const HashJoin *from) return newnode; } +/* + * _copyForeignJoin + */ +static ForeignJoin * +_copyForeignJoin(const ForeignJoin *from) +{ + ForeignJoin *newnode = makeNode(ForeignJoin); + + /* + * copy node superclass fields + */ + CopyJoinFields((const Join *) from, (Join *) newnode); + + /* + * copy remainder of node + */ + COPY_NODE_FIELD(fdw_private); + + return newnode; +} + /* * _copyMaterial @@ -4054,6 +4075,9 @@ copyObject(const void *from) case T_HashJoin: retval = _copyHashJoin(from); break; + case T_ForeignJoin: + retval = _copyForeignJoin(from); + break; case T_Material: retval = _copyMaterial(from); break; diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 1ff78eb..82c8624 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -623,6 +623,16 @@ _outHashJoin(StringInfo str, const HashJoin *node) } static void +_outForeignJoin(StringInfo str, const ForeignJoin *node) +{ + WRITE_NODE_TYPE("FOREIGNJOIN"); + + _outJoinPlanInfo(str, (const Join *) node); + + WRITE_NODE_FIELD(fdw_private); +} + +static void _outAgg(StringInfo str, const Agg *node) { int i; @@ -1668,6 +1678,16 @@ _outHashPath(StringInfo str, const HashPath *node) } static void +_outForeignJoinPath(StringInfo str, const ForeignJoinPath *node) +{ + WRITE_NODE_TYPE("FOREIGNJOINPATH"); + + _outJoinPathInfo(str, (const JoinPath *) node); + + WRITE_NODE_FIELD(fdw_private); +} + +static void _outPlannerGlobal(StringInfo str, const PlannerGlobal *node) { WRITE_NODE_TYPE("PLANNERGLOBAL"); @@ -1766,6 +1786,7 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node) WRITE_NODE_FIELD(subplan); WRITE_NODE_FIELD(subroot); WRITE_NODE_FIELD(subplan_params); + WRITE_OID_FIELD(serverid); /* we don't try to print fdwroutine or fdw_private */ WRITE_NODE_FIELD(baserestrictinfo); WRITE_NODE_FIELD(joininfo); @@ -2864,6 +2885,9 @@ _outNode(StringInfo str, const void *obj) case T_HashJoin: _outHashJoin(str, obj); break; + case T_ForeignJoin: + _outForeignJoin(str, obj); + break; case T_Agg: _outAgg(str, obj); break; @@ -3084,6 +3108,9 @@ _outNode(StringInfo str, const void *obj) case T_HashPath: _outHashPath(str, obj); break; + case T_ForeignJoinPath: + _outForeignJoinPath(str, obj); + break; case T_PlannerGlobal: _outPlannerGlobal(str, obj); break; diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c index be54f3d..faadefc 100644 --- a/src/backend/optimizer/path/joinpath.c +++ b/src/backend/optimizer/path/joinpath.c @@ -17,6 +17,7 @@ #include #include "executor/executor.h" +#include "foreign/fdwapi.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" @@ -50,7 +51,6 @@ static List *select_mergejoin_clauses(PlannerInfo *root, JoinType jointype, bool *mergejoin_allowed); - /* * add_paths_to_joinrel * Given a join relation and two component rels from which it can be made, @@ -207,7 +207,26 @@ add_paths_to_joinrel(PlannerInfo *root, extra_lateral_rels = NULL; /* - * 1. Consider mergejoin paths where both relations must be explicitly + * 1. Consider foreignjoin paths when both outer and inner relations are + * managed by same foreign-data wrapper. This is done preceding to any + * local join consideration because foreignjoin would be cheapst in most + * case when joining on remote side is possible. + */ + if (joinrel->fdwroutine && joinrel->fdwroutine->GetForeignJoinPath) + { + joinrel->fdwroutine->GetForeignJoinPath(root, + joinrel, + outerrel, + innerrel, + jointype, + sjinfo, + &semifactors, + restrictlist, + extra_lateral_rels); + } + + /* + * 2. Consider mergejoin paths where both relations must be explicitly * sorted. Skip this if we can't mergejoin. */ if (mergejoin_allowed) @@ -217,7 +236,7 @@ add_paths_to_joinrel(PlannerInfo *root, param_source_rels, extra_lateral_rels); /* - * 2. Consider paths where the outer relation need not be explicitly + * 3. Consider paths where the outer relation need not be explicitly * sorted. This includes both nestloops and mergejoins where the outer * path is already ordered. Again, skip this if we can't mergejoin. * (That's okay because we know that nestloop can't handle right/full @@ -232,7 +251,7 @@ add_paths_to_joinrel(PlannerInfo *root, #ifdef NOT_USED /* - * 3. Consider paths where the inner relation need not be explicitly + * 4. Consider paths where the inner relation need not be explicitly * sorted. This includes mergejoins only (nestloops were already built in * match_unsorted_outer). * @@ -250,7 +269,7 @@ add_paths_to_joinrel(PlannerInfo *root, #endif /* - * 4. Consider paths where both outer and inner relations must be hashed + * 5. Consider paths where both outer and inner relations must be hashed * before being joined. As above, disregard enable_hashjoin for full * joins, because there may be no other alternative. */ diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 4b641a2..4c28dcb 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -83,6 +83,9 @@ static MergeJoin *create_mergejoin_plan(PlannerInfo *root, MergePath *best_path, Plan *outer_plan, Plan *inner_plan); static HashJoin *create_hashjoin_plan(PlannerInfo *root, HashPath *best_path, Plan *outer_plan, Plan *inner_plan); +static ForeignJoin *create_foreignjoin_plan(PlannerInfo *root, + ForeignJoinPath *best_path, Plan *outer_plan, + Plan *inner_plan); static Node *replace_nestloop_params(PlannerInfo *root, Node *expr); static Node *replace_nestloop_params_mutator(Node *node, PlannerInfo *root); static void process_subquery_nestloop_params(PlannerInfo *root, @@ -235,6 +238,7 @@ create_plan_recurse(PlannerInfo *root, Path *best_path) case T_ForeignScan: plan = create_scan_plan(root, best_path); break; + case T_ForeignJoin: case T_HashJoin: case T_MergeJoin: case T_NestLoop: @@ -625,6 +629,13 @@ create_join_plan(PlannerInfo *root, JoinPath *best_path) outer_plan, inner_plan); break; + case T_ForeignJoin: + /* Create ForeignScan plan node for ForeignJoin path */ + plan = (Plan *) create_foreignjoin_plan(root, + (ForeignJoinPath *) best_path, + outer_plan, + inner_plan); + break; case T_NestLoop: /* Restore curOuterRels */ bms_free(root->curOuterRels); @@ -2524,6 +2535,28 @@ create_hashjoin_plan(PlannerInfo *root, return join_plan; } +static ForeignJoin * +create_foreignjoin_plan(PlannerInfo *root, + ForeignJoinPath *best_path, + Plan *outer_plan, + Plan *inner_plan) +{ + ForeignJoin *join_plan; + RelOptInfo *rel = best_path->jpath.path.parent; + + Assert(rel->fdwroutine); + Assert(rel->fdwroutine->GetForeignJoinPlan); + + join_plan = rel->fdwroutine->GetForeignJoinPlan(root, + best_path, + outer_plan, + inner_plan); + + copy_path_costsize(&join_plan->join.plan, &best_path->jpath.path); + + return join_plan; +} + /***************************************************************************** * @@ -3727,6 +3760,34 @@ make_mergejoin(List *tlist, return node; } +ForeignJoin * +make_foreignjoin(List *tlist, + List *joinclauses, + List *otherclauses, + Oid serverid, + List *fdwclauses, + List *fdw_private, + Plan *lefttree, + Plan *righttree, + JoinType jointype) +{ + ForeignJoin *node = makeNode(ForeignJoin); + Plan *plan = &node->join.plan; + + /* cost will be filled in by create_foreignjoin_plan */ + plan->targetlist = tlist; + plan->qual = otherclauses; + plan->lefttree = lefttree; + plan->righttree = righttree; + node->join.jointype = jointype; + node->join.joinqual = joinclauses; + node->serverid = serverid; + node->fdwclauses = fdwclauses; + node->fdw_private = fdw_private; + + return node; +} + /* * make_sort --- basic routine to build a Sort plan node * diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 9ddc8ad..a3501706 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -582,6 +582,7 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) case T_NestLoop: case T_MergeJoin: case T_HashJoin: + case T_ForeignJoin: set_join_references(root, (Join *) plan, rtoffset); break; diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c index 3e7dc85..1f4e361 100644 --- a/src/backend/optimizer/plan/subselect.c +++ b/src/backend/optimizer/plan/subselect.c @@ -2403,6 +2403,15 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params, &context); break; + case T_ForeignJoin: + /* + * TODO: consider ForeignJoin-specific information, see + * T_ForeignScan section above. + */ + finalize_primnode((Node *) ((Join *) plan)->joinqual, + &context); + break; + case T_Limit: finalize_primnode(((Limit *) plan)->limitOffset, &context); diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index 319e8b2..4e9fb44 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -1859,6 +1859,58 @@ create_hashjoin_path(PlannerInfo *root, } /* + * create_foreignjoin_path + * Creates a pathnode corresponding to a foreign join between two relations. + * Unlike similar funcitons for other join types, final_cost_foreignjoin is + * not called, so FDW have to take care of cost information. + * + * 'joinrel' is the join relation + * 'jointype' is the type of join required + * 'sjinfo' is extra info about the join for selectivity estimation + * 'semifactors' contains valid data if jointype is SEMI or ANTI + * 'outer_path' is the cheapest outer path + * 'inner_path' is the cheapest inner path + * 'restrict_clauses' are the RestrictInfo nodes to apply at the join + * 'required_outer' is the set of required outer rels + * 'foreignclauses' are the RestrictInfo nodes to use as foreign clauses + * (this should be a subset of the restrict_clauses list) + */ +ForeignJoinPath * +create_foreignjoin_path(PlannerInfo *root, + RelOptInfo *joinrel, + JoinType jointype, + SpecialJoinInfo *sjinfo, + SemiAntiJoinFactors *semifactors, + Path *outer_path, + Path *inner_path, + List *restrict_clauses, + List *pathkeys, + Relids required_outer) +{ + ForeignJoinPath *pathnode = makeNode(ForeignJoinPath); + + pathnode->jpath.path.pathtype = T_ForeignJoin; + pathnode->jpath.path.parent = joinrel; + pathnode->jpath.path.param_info = + get_joinrel_parampathinfo(root, + joinrel, + outer_path, + inner_path, + sjinfo, + required_outer, + &restrict_clauses); + pathnode->jpath.path.pathkeys = pathkeys; + pathnode->jpath.jointype = jointype; + pathnode->jpath.outerjoinpath = outer_path; + pathnode->jpath.innerjoinpath = inner_path; + pathnode->jpath.joinrestrictinfo = restrict_clauses; + + pathnode->fdw_private = NIL; + + return pathnode; +} + +/* * reparameterize_path * Attempt to modify a Path to have greater parameterization * diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index b2becfa..22b7523 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -27,6 +27,7 @@ #include "catalog/catalog.h" #include "catalog/heap.h" #include "foreign/fdwapi.h" +#include "foreign/foreign.h" #include "miscadmin.h" #include "nodes/makefuncs.h" #include "optimizer/clauses.h" @@ -378,9 +379,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->serverid = GetForeignTableServerOid(relation->rd_id); rel->fdwroutine = GetFdwRoutineForRelation(relation, true); + } else + { + rel->serverid = 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 4c76f54..278ca2e 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -121,6 +121,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind) rel->subplan = NULL; rel->subroot = NULL; rel->subplan_params = NIL; + rel->serverid = InvalidOid; rel->fdwroutine = NULL; rel->fdw_private = NULL; rel->baserestrictinfo = NIL; @@ -383,7 +384,17 @@ build_join_rel(PlannerInfo *root, joinrel->subplan = NULL; joinrel->subroot = NULL; joinrel->subplan_params = NIL; - joinrel->fdwroutine = NULL; + /* propagate common server information up to join relation */ + if (inner_rel->serverid == outer_rel->serverid) + { + joinrel->fdwroutine = inner_rel->fdwroutine; + joinrel->serverid = inner_rel->serverid; + } + else + { + joinrel->serverid = InvalidOid; + joinrel->fdwroutine = NULL; + } joinrel->fdw_private = NULL; joinrel->baserestrictinfo = NIL; joinrel->baserestrictcost.startup = 0; diff --git a/src/include/executor/nodeForeignjoin.h b/src/include/executor/nodeForeignjoin.h new file mode 100644 index 0000000..802498c --- /dev/null +++ b/src/include/executor/nodeForeignjoin.h @@ -0,0 +1,24 @@ +/*------------------------------------------------------------------------- + * + * nodeForeignjoin.h + * + * + * + * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/executor/nodeForeignjoin.h + * + *------------------------------------------------------------------------- + */ +#ifndef NODEFOREIGNJOIN_H +#define NODEFOREIGNJOIN_H + +#include "nodes/execnodes.h" + +extern ForeignJoinState *ExecInitForeignJoin(ForeignJoin *node, EState *estate, int eflags); +extern TupleTableSlot *ExecForeignJoin(ForeignJoinState *node); +extern void ExecEndForeignJoin(ForeignJoinState *node); +extern void ExecReScanForeignJoin(ForeignJoinState *node); + +#endif /* NODEFOREIGNJOIN_H */ diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h index dc0a7fc7..56fc8e0 100644 --- a/src/include/foreign/fdwapi.h +++ b/src/include/foreign/fdwapi.h @@ -82,6 +82,29 @@ typedef void (*EndForeignModify_function) (EState *estate, typedef int (*IsForeignRelUpdatable_function) (Relation rel); +typedef void (*GetForeignJoinPath_function ) (PlannerInfo *root, + RelOptInfo *joinrel, + RelOptInfo *outerrel, + RelOptInfo *innerrel, + JoinType jointype, + SpecialJoinInfo *sjinfo, + SemiAntiJoinFactors *semifactors, + List *restrictlist, + Relids extra_lateral_rels); + +typedef ForeignJoin *(*GetForeignJoinPlan_function) (PlannerInfo *root, + ForeignJoinPath *best_path, + Plan *outerplan, + Plan *innerplan); + +typedef void (*BeginForeignJoin_function) (ForeignJoinState *node, + int eflags); +typedef TupleTableSlot *(*IterateForeignJoin_function) (ForeignJoinState *node); + +typedef void (*ReScanForeignJoin_function) (ForeignJoinState *node); + +typedef void (*EndForeignJoin_function) (ForeignJoinState *node); + typedef void (*ExplainForeignScan_function) (ForeignScanState *node, struct ExplainState *es); @@ -150,12 +173,22 @@ typedef struct FdwRoutine /* Support functions for IMPORT FOREIGN SCHEMA */ ImportForeignSchema_function ImportForeignSchema; + + /* Support functions for join push-down */ + GetForeignJoinPath_function GetForeignJoinPath; + GetForeignJoinPlan_function GetForeignJoinPlan; + BeginForeignJoin_function BeginForeignJoin; + IterateForeignJoin_function IterateForeignJoin; + ReScanForeignJoin_function ReScanForeignJoin; + EndForeignJoin_function EndForeignJoin; + } FdwRoutine; /* Functions in foreign/foreign.c */ extern FdwRoutine *GetFdwRoutine(Oid fdwhandler); extern FdwRoutine *GetFdwRoutineByRelId(Oid relid); +extern FdwRoutine * GetFdwRoutineByServerId(Oid serverid); extern FdwRoutine *GetFdwRoutineForRelation(Relation relation, bool makecopy); extern bool IsImportableForeignTable(const char *tablename, ImportForeignSchemaStmt *stmt); diff --git a/src/include/foreign/foreign.h b/src/include/foreign/foreign.h index ac080d7..b9e120a 100644 --- a/src/include/foreign/foreign.h +++ b/src/include/foreign/foreign.h @@ -75,6 +75,7 @@ extern ForeignDataWrapper *GetForeignDataWrapper(Oid fdwid); extern ForeignDataWrapper *GetForeignDataWrapperByName(const char *name, bool missing_ok); extern ForeignTable *GetForeignTable(Oid relid); +extern Oid GetForeignTableServerOid(Oid relid); extern List *GetForeignColumnOptions(Oid relid, AttrNumber attnum); diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index b271f21..dd2f69c 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -1636,6 +1636,21 @@ typedef struct HashJoinState bool hj_OuterNotEmpty; } HashJoinState; +/* ---------------- + * ForeignJoinState information + * + * fdwroutine handler functions used to process the join + * fdw_state FDW-private state information + * ---------------- + */ +typedef struct ForeignJoinState +{ + JoinState js; /* its first field is NodeTag */ + /* use struct pointer to avoid including fdwapi.h here */ + struct FdwRoutine *fdwroutine; + void *fdw_state; /* foreign-data wrapper can keep state here */ +} ForeignJoinState; + /* ---------------------------------------------------------------- * Materialization State Information diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 154d943..c81eff3 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -66,6 +66,7 @@ typedef enum NodeTag T_NestLoop, T_MergeJoin, T_HashJoin, + T_ForeignJoin, T_Material, T_Sort, T_Group, @@ -111,6 +112,7 @@ typedef enum NodeTag T_NestLoopState, T_MergeJoinState, T_HashJoinState, + T_ForeignJoinState, T_MaterialState, T_SortState, T_GroupState, @@ -222,6 +224,7 @@ typedef enum NodeTag T_NestPath, T_MergePath, T_HashPath, + T_ForeignJoinPath, T_TidPath, T_ForeignPath, T_AppendPath, diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 1839494..bb741ae 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -569,6 +569,18 @@ typedef struct HashJoin } HashJoin; /* ---------------- + * foreign join node + * ---------------- + */ +typedef struct ForeignJoin +{ + Join join; + Oid serverid; + List *fdwclauses; /* expressions that FDW may evaluate */ + List *fdw_private; +} ForeignJoin; + +/* ---------------- * materialization node * ---------------- */ diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index f1a0504..71ac248 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -364,13 +364,14 @@ typedef struct PlannerInfo * subplan - plan for subquery (NULL if it's not a subquery) * subroot - PlannerInfo for subquery (NULL if it's not a subquery) * subplan_params - list of PlannerParamItems to be passed to subquery + * serverid - OID of server, if foreign table (else InvalidOid) * fdwroutine - function hooks for FDW, if foreign table (else NULL) * fdw_private - private state for FDW, if foreign table (else NULL) * * Note: for a subquery, tuples, subplan, subroot are not set immediately * upon creation of the RelOptInfo object; they are filled in when - * set_subquery_pathlist processes the object. Likewise, fdwroutine - * and fdw_private are filled during initial path creation. + * set_subquery_pathlist processes the object. Likewise, serverid, + * fdwroutine, and fdw_private are filled during initial path creation. * * For otherrels that are appendrel members, these fields are filled * in just as for a baserel. @@ -459,6 +460,7 @@ typedef struct RelOptInfo PlannerInfo *subroot; /* if subquery */ List *subplan_params; /* if subquery */ /* use "struct FdwRoutine" to avoid including fdwapi.h here */ + Oid serverid; /* if foreign table */ struct FdwRoutine *fdwroutine; /* if foreign table */ void *fdw_private; /* if foreign table */ @@ -1053,6 +1055,22 @@ typedef struct HashPath } HashPath; /* + * ForeignJoinPath represents a join between two relations consist of foreign + * table. + * + * fdw_private stores FDW private data about the join. While fdw_private is + * not actually touched by the core code during normal operations, it's + * generally a good idea to use a representation that can be dumped by + * nodeToString(), so that you can examine the structure during debugging + * with tools like pprint(). + */ +typedef struct ForeignJoinPath +{ + JoinPath jpath; + List *fdw_private; +} ForeignJoinPath; + +/* * Restriction clause info. * * We create one of these for each AND sub-clause of a restriction condition diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index 26b17f5..7a1f236 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -124,6 +124,17 @@ extern HashPath *create_hashjoin_path(PlannerInfo *root, Relids required_outer, List *hashclauses); +extern ForeignJoinPath *create_foreignjoin_path(PlannerInfo *root, + RelOptInfo *joinrel, + JoinType jointype, + SpecialJoinInfo *sjinfo, + SemiAntiJoinFactors *semifactors, + Path *outer_path, + Path *inner_path, + List *restrict_clauses, + List *pathkeys, + Relids required_outer); + extern Path *reparameterize_path(PlannerInfo *root, Path *path, Relids required_outer, double loop_count); diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h index 3fdc2cb..a0e5788 100644 --- a/src/include/optimizer/planmain.h +++ b/src/include/optimizer/planmain.h @@ -45,6 +45,10 @@ extern SubqueryScan *make_subqueryscan(List *qptlist, List *qpqual, Index scanrelid, Plan *subplan); extern ForeignScan *make_foreignscan(List *qptlist, List *qpqual, Index scanrelid, List *fdw_exprs, List *fdw_private); +extern ForeignJoin *make_foreignjoin(List *tlist, List *joinclauses, + List *otherclauses, Oid serverid, List *fdwclauses, + List *fdw_private, Plan *lefttree, Plan *righttree, + JoinType jointype); extern Append *make_append(List *appendplans, List *tlist); extern RecursiveUnion *make_recursive_union(List *tlist, Plan *lefttree, Plan *righttree, int wtParam,