diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c index a75462b..f6d6936 100644 --- a/contrib/postgres_fdw/deparse.c +++ b/contrib/postgres_fdw/deparse.c @@ -86,7 +86,7 @@ typedef struct foreign_loc_cxt typedef struct deparse_expr_cxt { PlannerInfo *root; /* global planner state */ - RelOptInfo *foreignrel; /* the foreign relation we are planning for */ + Relids rels; /* list of foreign tables to be deparsed */ StringInfo buf; /* output buffer to append to */ List **params_list; /* exprs that will become remote Params */ } deparse_expr_cxt; @@ -108,6 +108,7 @@ static void deparseTargetList(StringInfo buf, Index rtindex, Relation rel, Bitmapset *attrs_used, + const char *alias, List **retrieved_attrs); static void deparseReturningList(StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, @@ -115,7 +116,7 @@ static void deparseReturningList(StringInfo buf, PlannerInfo *root, List *returningList, List **retrieved_attrs); static void deparseColumnRef(StringInfo buf, int varno, int varattno, - PlannerInfo *root); + PlannerInfo *root, const char *alias); static void deparseRelation(StringInfo buf, Relation rel); static void deparseExpr(Expr *expr, deparse_expr_cxt *context); static void deparseVar(Var *node, deparse_expr_cxt *context); @@ -679,33 +680,119 @@ is_builtin(Oid oid) void deparseSelectSql(StringInfo buf, PlannerInfo *root, - RelOptInfo *baserel, - Bitmapset *attrs_used, - List **retrieved_attrs) + List *rels) { - RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root); - Relation rel; + StringInfoData frombuf; + ListCell *lc; + bool first_rel = true; + Relids relids = NULL; - /* - * Core code already has some lock on each rel being planned, so we can - * use NoLock here. - */ - rel = heap_open(rte->relid, NoLock); + initStringInfo(&frombuf); - /* - * Construct SELECT list - */ - appendStringInfoString(buf, "SELECT "); - deparseTargetList(buf, root, baserel->relid, rel, attrs_used, - retrieved_attrs); + /* Construct list of relid for deparsing query contains multiple tables. */ + foreach(lc, rels) + { + PgFdwDeparseRel *dr = (PgFdwDeparseRel *) lfirst(lc); + relids = bms_add_member(relids, dr->baserel->relid); + } - /* - * Construct FROM clause - */ - appendStringInfoString(buf, " FROM "); - deparseRelation(buf, rel); + /* Loop through relation list and deparse SELECT query. */ + foreach(lc, rels) + { + PgFdwDeparseRel *dr = (PgFdwDeparseRel *) lfirst(lc); + RangeTblEntry *rte = planner_rt_fetch(dr->baserel->relid, root); + Relation rel; + const char *alias; - heap_close(rel, NoLock); + /* + * Core code already has some lock on each rel being planned, so we can + * use NoLock here. + */ + rel = heap_open(rte->relid, NoLock); + + /* + * Add alias only when we have multiple relations. + */ + if (list_length(rels) > 1 && rte->alias) + alias = rte->alias->aliasname; + else + alias = NULL; + + /* + * Construct SELECT list + */ + if (first_rel) + appendStringInfoString(buf, "SELECT "); + else + appendStringInfoString(buf, ", "); + deparseTargetList(buf, root, dr->baserel->relid, rel, dr->attrs_used, + alias, dr->retrieved_attrs); + + /* + * Construct FROM clause + */ + if (first_rel) + appendStringInfoString(&frombuf, " FROM "); + else + { + switch (dr->jointype) + { + case JOIN_INNER: + if (dr->joinclauses) + appendStringInfoString(&frombuf, " INNER JOIN "); + else + /* Currently cross join is not pushed down, though. */ + appendStringInfoString(&frombuf, " CROSS JOIN "); + break; + case JOIN_LEFT: + appendStringInfoString(&frombuf, " LEFT JOIN "); + break; + case JOIN_FULL: + appendStringInfoString(&frombuf, " FULL JOIN "); + break; + case JOIN_RIGHT: + appendStringInfoString(&frombuf, " RIGHT JOIN "); + break; + default: + elog(ERROR, "unsupported join type for deparse: %d", + dr->jointype); + break; + } + } + deparseRelation(&frombuf, rel); + if (alias) + appendStringInfo(&frombuf, " %s", alias); + + if (!first_rel && dr->joinclauses) + { + ListCell *lc; + bool first = true; + + appendStringInfoString(&frombuf, " ON "); + + foreach(lc, dr->joinclauses) + { + deparse_expr_cxt context; + Expr *expr = (Expr *) lfirst(lc); + + context.root = root; + context.rels = relids; + context.buf = &frombuf; + context.params_list = NULL; + + if (!first) + appendStringInfoString(&frombuf, " AND "); + deparseExpr(expr, &context); + first = false; + } + } + + heap_close(rel, NoLock); + first_rel = false; + } + + appendStringInfoString(buf, frombuf.data); + pfree(frombuf.data); } /* @@ -721,6 +808,7 @@ deparseTargetList(StringInfo buf, Index rtindex, Relation rel, Bitmapset *attrs_used, + const char *alias, List **retrieved_attrs) { TupleDesc tupdesc = RelationGetDescr(rel); @@ -751,7 +839,7 @@ deparseTargetList(StringInfo buf, appendStringInfoString(buf, ", "); first = false; - deparseColumnRef(buf, rtindex, i, root); + deparseColumnRef(buf, rtindex, i, root, alias); *retrieved_attrs = lappend_int(*retrieved_attrs, i); } @@ -768,6 +856,8 @@ deparseTargetList(StringInfo buf, appendStringInfoString(buf, ", "); first = false; + if (alias) + appendStringInfo(buf, "%s.", alias); appendStringInfoString(buf, "ctid"); *retrieved_attrs = lappend_int(*retrieved_attrs, @@ -796,7 +886,7 @@ deparseTargetList(StringInfo buf, void appendWhereClause(StringInfo buf, PlannerInfo *root, - RelOptInfo *baserel, + Relids relids, List *exprs, bool is_first, List **params) @@ -810,7 +900,7 @@ appendWhereClause(StringInfo buf, /* Set up context struct for recursion */ context.root = root; - context.foreignrel = baserel; + context.rels = relids; context.buf = buf; context.params_list = params; @@ -870,7 +960,7 @@ deparseInsertSql(StringInfo buf, PlannerInfo *root, appendStringInfoString(buf, ", "); first = false; - deparseColumnRef(buf, rtindex, attnum, root); + deparseColumnRef(buf, rtindex, attnum, root, NULL); } appendStringInfoString(buf, ") VALUES ("); @@ -928,7 +1018,7 @@ deparseUpdateSql(StringInfo buf, PlannerInfo *root, appendStringInfoString(buf, ", "); first = false; - deparseColumnRef(buf, rtindex, attnum, root); + deparseColumnRef(buf, rtindex, attnum, root, NULL); appendStringInfo(buf, " = $%d", pindex); pindex++; } @@ -993,7 +1083,7 @@ deparseReturningList(StringInfo buf, PlannerInfo *root, if (attrs_used != NULL) { appendStringInfoString(buf, " RETURNING "); - deparseTargetList(buf, root, rtindex, rel, attrs_used, + deparseTargetList(buf, root, rtindex, rel, attrs_used, NULL, retrieved_attrs); } else @@ -1088,7 +1178,8 @@ deparseAnalyzeSql(StringInfo buf, Relation rel, List **retrieved_attrs) * If it has a column_name FDW option, use that instead of attribute name. */ static void -deparseColumnRef(StringInfo buf, int varno, int varattno, PlannerInfo *root) +deparseColumnRef(StringInfo buf, int varno, int varattno, PlannerInfo *root, + const char *alias) { RangeTblEntry *rte; char *colname = NULL; @@ -1124,6 +1215,8 @@ deparseColumnRef(StringInfo buf, int varno, int varattno, PlannerInfo *root) if (colname == NULL) colname = get_relid_attribute_name(rte->relid, varattno); + if (alias) + appendStringInfo(buf, "%s.", alias); appendStringInfoString(buf, quote_identifier(colname)); } @@ -1270,12 +1363,46 @@ static void deparseVar(Var *node, deparse_expr_cxt *context) { StringInfo buf = context->buf; + int i; + RelOptInfo *rel = NULL; + RangeTblEntry *rte = NULL; - if (node->varno == context->foreignrel->relid && - node->varlevelsup == 0) + /* Find RangeTblEntry contains given Var to determine alias name. */ + if (bms_is_member(node->varno, context->rels) && node->varlevelsup == 0) { - /* Var belongs to foreign table */ - deparseColumnRef(buf, node->varno, node->varattno, context->root); + for (i = 1; i < context->root->simple_rel_array_size; i++) + { + /* Skip empty slot */ + if (context->root->simple_rel_array[i] == NULL) + continue; + + if (context->root->simple_rel_array[i]->relid == node->varno) + { + rel = context->root->simple_rel_array[i]; + rte = context->root->simple_rte_array[i]; + break; + } + } + } + + /* + * If the Var is in current level (not in outer subquery), simply deparse + * it. + */ + if (rel) + { + const char *alias; + + /* + * Deparse Var belongs to foreign tables in context->rels, with alias + * name if we are deparsing multiple foreign tables. + */ + if (bms_num_members(context->rels) > 1 && rte->alias) + alias = rte->alias->aliasname; + else + alias = NULL; + deparseColumnRef(buf, node->varno, node->varattno, context->root, + alias); } else { @@ -1849,3 +1976,4 @@ printRemotePlaceholder(Oid paramtype, int32 paramtypmod, appendStringInfo(buf, "((SELECT null::%s)::%s)", ptypename, ptypename); } + diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index c3039a6..aaae2bb 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -288,6 +288,16 @@ 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, + List *restrictlisti, + JoinType jointype, + SpecialJoinInfo *sjinfo, + SemiAntiJoinFactors *semifactors, + Relids param_source_rels, + Relids extra_lateral_rels); /* * Helper functions @@ -329,6 +339,17 @@ static HeapTuple make_tuple_from_result_row(PGresult *res, MemoryContext temp_context); static void conversion_error_callback(void *arg); +extern void _PG_init(void); + +static set_join_pathlist_hook_type prev_set_join_pathlist_hook; + +void +_PG_init(void) +{ + prev_set_join_pathlist_hook = set_join_pathlist_hook; + set_join_pathlist_hook = postgresGetForeignJoinPath; +} + /* * Foreign-data wrapper handler function: return a struct with pointers @@ -752,6 +773,7 @@ postgresGetForeignPlan(PlannerInfo *root, List *retrieved_attrs; StringInfoData sql; ListCell *lc; + PgFdwDeparseRel dr; /* * Separate the scan_clauses into those that can be executed remotely and @@ -797,11 +819,15 @@ postgresGetForeignPlan(PlannerInfo *root, * expressions to be sent as parameters. */ initStringInfo(&sql); - deparseSelectSql(&sql, root, baserel, fpinfo->attrs_used, - &retrieved_attrs); + dr.baserel = baserel; + dr.jointype = JOIN_INNER; + dr.joinclauses = NIL; + dr.attrs_used = fpinfo->attrs_used; + dr.retrieved_attrs = &retrieved_attrs; + deparseSelectSql(&sql, root, list_make1(&dr)); if (remote_conds) - appendWhereClause(&sql, root, baserel, remote_conds, - true, ¶ms_list); + appendWhereClause(&sql, root, bms_add_member(NULL, baserel->relid), + remote_conds, true, ¶ms_list); /* * Add FOR UPDATE/SHARE if appropriate. We apply locking during the @@ -1725,10 +1751,12 @@ estimate_path_cost_size(PlannerInfo *root, List *remote_join_conds; List *local_join_conds; StringInfoData sql; + Relids relids; List *retrieved_attrs; PGconn *conn; Selectivity local_sel; QualCost local_cost; + PgFdwDeparseRel dr; /* * join_conds might contain both clauses that are safe to send across, @@ -1743,14 +1771,19 @@ estimate_path_cost_size(PlannerInfo *root, * dummy values. */ initStringInfo(&sql); + dr.baserel = baserel; + dr.jointype = JOIN_INNER; + dr.joinclauses = NIL; + dr.attrs_used = fpinfo->attrs_used; + dr.retrieved_attrs = &retrieved_attrs; appendStringInfoString(&sql, "EXPLAIN "); - deparseSelectSql(&sql, root, baserel, fpinfo->attrs_used, - &retrieved_attrs); + deparseSelectSql(&sql, root, list_make1(&dr)); + relids = bms_add_member(NULL, baserel->relid); if (fpinfo->remote_conds) - appendWhereClause(&sql, root, baserel, fpinfo->remote_conds, + appendWhereClause(&sql, root, relids, fpinfo->remote_conds, true, NULL); if (remote_join_conds) - appendWhereClause(&sql, root, baserel, remote_join_conds, + appendWhereClause(&sql, root, relids, remote_join_conds, (fpinfo->remote_conds == NIL), NULL); /* Get the remote estimate */ @@ -2835,6 +2868,118 @@ postgresImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid) } /* + * postgresGetForeignJoinPath + * Add possible ForeignJoinPath to joinrel. + * + */ +static void +postgresGetForeignJoinPath(PlannerInfo *root, + RelOptInfo *joinrel, + RelOptInfo *outerrel, + RelOptInfo *innerrel, + List *restrictlist, + JoinType jointype, + SpecialJoinInfo *sjinfo, + SemiAntiJoinFactors *semifactors, + Relids param_source_rels, + Relids extra_lateral_rels) +{ + int i; + bool rte_found; + Oid serverid = InvalidOid; + Oid checkAsUser = InvalidOid; + Path *outerpath = outerrel->cheapest_total_path; + Path *innerpath = innerrel->cheapest_total_path; + ForeignJoinPath *joinpath; + Relids required_outer; + List *fdw_private = NIL; + + /* Skip considering reversed join combination */ + elog(DEBUG1, "%s() outer: %d, inner: %d", + __func__, outerrel->relid, innerrel->relid); + if (outerrel->relid < innerrel->relid) + return; + + /* At the moment we support only joins between foreign tables. */ + if (outerrel->reloptkind != RELOPT_BASEREL || + innerrel->reloptkind != RELOPT_BASEREL) + return; + + /* + * We support all outer joins in addition to inner join. + */ + if (jointype != JOIN_INNER && jointype != JOIN_LEFT && + jointype != JOIN_RIGHT && jointype != JOIN_FULL) + return; + + /* + * Note that CROSS JOIN (cartesian product) is transformed to JOIN_INNER + * with empty restrictlist. Pushing down CROSS JOIN produces more result + * than retrieving each tables separately, so we don't push down such joins. + */ + if (jointype == JOIN_INNER && !restrictlist) + return; + + /* + * All relations in the join must belong to same server, and have same + * checkAsUser to use one connection to execute SQL for the join. + */ + rte_found = false; + for (i = 1; i < root->simple_rel_array_size; i++) + { + RangeTblEntry *rte; + ForeignTable *ft; + + if (!bms_is_member(i, joinrel->relids)) + continue; + + rte = root->simple_rte_array[i]; + if (rte == NULL) + continue; + + ft = GetForeignTable(rte->relid); + if (rte_found) + { + if (serverid != ft->serverid) + return; + if (checkAsUser != rte->checkAsUser) + return; + } + else + { + checkAsUser = rte->checkAsUser; + serverid = ft->serverid; + rte_found = true; + } + } + fdw_private = lappend_oid(fdw_private, checkAsUser); + + /* + * Create a new join path and add it to the joinrel which represents a join + * between foreign tables. + */ + 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 determine cost and rows of the join. */ + + /* Add generated path into joinrel by add_path(). */ + add_path(joinrel, (Path *) joinpath); + + /* TODO consider parameterized paths */ +} + + +/* * Create a tuple from the specified row of the PGresult. * * rel is the local representation of the foreign table, attinmeta is diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h index 0382c55..dcfcb77 100644 --- a/contrib/postgres_fdw/postgres_fdw.h +++ b/contrib/postgres_fdw/postgres_fdw.h @@ -39,6 +39,13 @@ extern int ExtractConnectionOptions(List *defelems, const char **values); /* in deparse.c */ +typedef struct PgFdwDeparseRel { + RelOptInfo *baserel; + JoinType jointype; + List *joinclauses; + Bitmapset *attrs_used; + List **retrieved_attrs; +} PgFdwDeparseRel; extern void classifyConditions(PlannerInfo *root, RelOptInfo *baserel, List *input_conds, @@ -49,12 +56,10 @@ extern bool is_foreign_expr(PlannerInfo *root, Expr *expr); extern void deparseSelectSql(StringInfo buf, PlannerInfo *root, - RelOptInfo *baserel, - Bitmapset *attrs_used, - List **retrieved_attrs); + List *rels); extern void appendWhereClause(StringInfo buf, PlannerInfo *root, - RelOptInfo *baserel, + Relids relids, List *exprs, bool is_first, List **params); diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c index 457cbab..7023a40 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. */ @@ -309,11 +332,15 @@ GetFdwRoutine(Oid fdwhandler) FdwRoutine * GetFdwRoutineByServer(Oid serverid) { + 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)); @@ -323,6 +350,16 @@ GetFdwRoutineByServer(Oid serverid) fdwid = serverform->srvfdw; ReleaseSysCache(tp); + return GetFdwRoutineByFdwId(fdwid); +} + +FdwRoutine * +GetFdwRoutineByFdwId(Oid fdwid) +{ + HeapTuple tp; + Form_pg_foreign_data_wrapper fdwform; + Oid fdwhandler; + /* Get handler function OID for the FDW. */ tp = SearchSysCache1(FOREIGNDATAWRAPPEROID, ObjectIdGetDatum(fdwid)); if (!HeapTupleIsValid(tp)) diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index d799eb8..b9ef533 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -1702,6 +1702,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"); @@ -1800,6 +1810,7 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node) WRITE_NODE_FIELD(subplan); WRITE_NODE_FIELD(subroot); WRITE_NODE_FIELD(subplan_params); + WRITE_OID_FIELD(fdwid); /* we don't try to print fdwroutine or fdw_private */ WRITE_NODE_FIELD(baserestrictinfo); WRITE_NODE_FIELD(joininfo); @@ -3124,6 +3135,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/costsize.c b/src/backend/optimizer/path/costsize.c index 659daa2..637e2d8 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -1782,8 +1782,8 @@ final_cost_nestloop(PlannerInfo *root, NestPath *path, SpecialJoinInfo *sjinfo, SemiAntiJoinFactors *semifactors) { - Path *outer_path = path->outerjoinpath; - Path *inner_path = path->innerjoinpath; + Path *outer_path = path->jpath.outerjoinpath; + Path *inner_path = path->jpath.innerjoinpath; double outer_path_rows = outer_path->rows; double inner_path_rows = inner_path->rows; Cost startup_cost = workspace->startup_cost; @@ -1794,10 +1794,10 @@ final_cost_nestloop(PlannerInfo *root, NestPath *path, double ntuples; /* Mark the path with the correct row estimate */ - if (path->path.param_info) - path->path.rows = path->path.param_info->ppi_rows; + if (path->jpath.path.param_info) + path->jpath.path.rows = path->jpath.path.param_info->ppi_rows; else - path->path.rows = path->path.parent->rows; + path->jpath.path.rows = path->jpath.path.parent->rows; /* * We could include disable_cost in the preliminary estimate, but that @@ -1809,7 +1809,7 @@ final_cost_nestloop(PlannerInfo *root, NestPath *path, /* cost of source data */ - if (path->jointype == JOIN_SEMI || path->jointype == JOIN_ANTI) + if (path->jpath.jointype == JOIN_SEMI || path->jpath.jointype == JOIN_ANTI) { double outer_matched_rows = workspace->outer_matched_rows; Selectivity inner_scan_frac = workspace->inner_scan_frac; @@ -1856,13 +1856,13 @@ final_cost_nestloop(PlannerInfo *root, NestPath *path, } /* CPU costs */ - cost_qual_eval(&restrict_qual_cost, path->joinrestrictinfo, root); + cost_qual_eval(&restrict_qual_cost, path->jpath.joinrestrictinfo, root); startup_cost += restrict_qual_cost.startup; cpu_per_tuple = cpu_tuple_cost + restrict_qual_cost.per_tuple; run_cost += cpu_per_tuple * ntuples; - path->path.startup_cost = startup_cost; - path->path.total_cost = startup_cost + run_cost; + path->jpath.path.startup_cost = startup_cost; + path->jpath.path.total_cost = startup_cost + run_cost; } /* @@ -3306,14 +3306,14 @@ compute_semi_anti_join_factors(PlannerInfo *root, static bool has_indexed_join_quals(NestPath *joinpath) { - Relids joinrelids = joinpath->path.parent->relids; - Path *innerpath = joinpath->innerjoinpath; + Relids joinrelids = joinpath->jpath.path.parent->relids; + Path *innerpath = joinpath->jpath.innerjoinpath; List *indexclauses; bool found_one; ListCell *lc; /* If join still has quals to evaluate, it's not fast */ - if (joinpath->joinrestrictinfo != NIL) + if (joinpath->jpath.joinrestrictinfo != NIL) return false; /* Nor if the inner path isn't parameterized at all */ if (innerpath->param_info == NULL) diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c index 030158d..29ee414 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" @@ -52,7 +53,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, diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 9683560..fc3ef81 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -83,11 +83,11 @@ static CustomScan *create_customscan_plan(PlannerInfo *root, CustomPath *best_path, List *tlist, List *scan_clauses); static NestLoop *create_nestloop_plan(PlannerInfo *root, NestPath *best_path, - Plan *outer_plan, Plan *inner_plan); + List *tlist, Plan *outer_plan, Plan *inner_plan); static MergeJoin *create_mergejoin_plan(PlannerInfo *root, MergePath *best_path, - Plan *outer_plan, Plan *inner_plan); + List *tlist, Plan *outer_plan, Plan *inner_plan); static HashJoin *create_hashjoin_plan(PlannerInfo *root, HashPath *best_path, - Plan *outer_plan, Plan *inner_plan); + List *tlist, 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, @@ -238,6 +238,7 @@ create_plan_recurse(PlannerInfo *root, Path *best_path) case T_CteScan: case T_WorkTableScan: case T_ForeignScan: + case T_ForeignJoinPath: case T_CustomScan: plan = create_scan_plan(root, best_path); break; @@ -409,6 +410,7 @@ create_scan_plan(PlannerInfo *root, Path *best_path) break; case T_ForeignScan: + case T_ForeignJoinPath: plan = (Plan *) create_foreignscan_plan(root, (ForeignPath *) best_path, tlist, @@ -611,6 +613,7 @@ create_gating_plan(PlannerInfo *root, Plan *plan, List *quals) static Plan * create_join_plan(PlannerInfo *root, JoinPath *best_path) { + List *tlist; Plan *outer_plan; Plan *inner_plan; Plan *plan; @@ -625,27 +628,34 @@ create_join_plan(PlannerInfo *root, JoinPath *best_path) inner_plan = create_plan_recurse(root, best_path->innerjoinpath); + if (best_path->path.pathtype == T_NestLoop) + { + /* Restore curOuterRels */ + bms_free(root->curOuterRels); + root->curOuterRels = saveOuterRels; + } + tlist = build_path_tlist(root, &best_path->path); + switch (best_path->path.pathtype) { case T_MergeJoin: plan = (Plan *) create_mergejoin_plan(root, (MergePath *) best_path, + tlist, outer_plan, inner_plan); break; case T_HashJoin: plan = (Plan *) create_hashjoin_plan(root, (HashPath *) best_path, + tlist, outer_plan, inner_plan); break; case T_NestLoop: - /* Restore curOuterRels */ - bms_free(root->curOuterRels); - root->curOuterRels = saveOuterRels; - plan = (Plan *) create_nestloop_plan(root, (NestPath *) best_path, + tlist, outer_plan, inner_plan); break; @@ -2115,12 +2125,12 @@ create_customscan_plan(PlannerInfo *root, CustomPath *best_path, static NestLoop * create_nestloop_plan(PlannerInfo *root, NestPath *best_path, + List *tlist, Plan *outer_plan, Plan *inner_plan) { NestLoop *join_plan; - List *tlist = build_path_tlist(root, &best_path->path); - List *joinrestrictclauses = best_path->joinrestrictinfo; + List *joinrestrictclauses = best_path->jpath.joinrestrictinfo; List *joinclauses; List *otherclauses; Relids outerrelids; @@ -2134,7 +2144,7 @@ create_nestloop_plan(PlannerInfo *root, /* Get the join qual clauses (in plain expression form) */ /* Any pseudoconstant clauses are ignored here */ - if (IS_OUTER_JOIN(best_path->jointype)) + if (IS_OUTER_JOIN(best_path->jpath.jointype)) { extract_actual_join_clauses(joinrestrictclauses, &joinclauses, &otherclauses); @@ -2147,7 +2157,7 @@ create_nestloop_plan(PlannerInfo *root, } /* Replace any outer-relation variables with nestloop params */ - if (best_path->path.param_info) + if (best_path->jpath.path.param_info) { joinclauses = (List *) replace_nestloop_params(root, (Node *) joinclauses); @@ -2159,7 +2169,7 @@ create_nestloop_plan(PlannerInfo *root, * Identify any nestloop parameters that should be supplied by this join * node, and move them from root->curOuterParams to the nestParams list. */ - outerrelids = best_path->outerjoinpath->parent->relids; + outerrelids = best_path->jpath.outerjoinpath->parent->relids; nestParams = NIL; prev = NULL; for (cell = list_head(root->curOuterParams); cell; cell = next) @@ -2196,9 +2206,9 @@ create_nestloop_plan(PlannerInfo *root, nestParams, outer_plan, inner_plan, - best_path->jointype); + best_path->jpath.jointype); - copy_path_costsize(&join_plan->join.plan, &best_path->path); + copy_path_costsize(&join_plan->join.plan, &best_path->jpath.path); return join_plan; } @@ -2206,10 +2216,10 @@ create_nestloop_plan(PlannerInfo *root, static MergeJoin * create_mergejoin_plan(PlannerInfo *root, MergePath *best_path, + List *tlist, Plan *outer_plan, Plan *inner_plan) { - List *tlist = build_path_tlist(root, &best_path->jpath.path); List *joinclauses; List *otherclauses; List *mergeclauses; @@ -2501,10 +2511,10 @@ create_mergejoin_plan(PlannerInfo *root, static HashJoin * create_hashjoin_plan(PlannerInfo *root, HashPath *best_path, + List *tlist, Plan *outer_plan, Plan *inner_plan) { - List *tlist = build_path_tlist(root, &best_path->jpath.path); List *joinclauses; List *otherclauses; List *hashclauses; diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index 319e8b2..dd81764 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -1710,9 +1710,9 @@ create_nestloop_path(PlannerInfo *root, restrict_clauses = jclauses; } - pathnode->path.pathtype = T_NestLoop; - pathnode->path.parent = joinrel; - pathnode->path.param_info = + pathnode->jpath.path.pathtype = T_NestLoop; + pathnode->jpath.path.parent = joinrel; + pathnode->jpath.path.param_info = get_joinrel_parampathinfo(root, joinrel, outer_path, @@ -1720,11 +1720,11 @@ create_nestloop_path(PlannerInfo *root, sjinfo, required_outer, &restrict_clauses); - pathnode->path.pathkeys = pathkeys; - pathnode->jointype = jointype; - pathnode->outerjoinpath = outer_path; - pathnode->innerjoinpath = inner_path; - pathnode->joinrestrictinfo = 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; final_cost_nestloop(root, pathnode, workspace, sjinfo, semifactors); @@ -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_ForeignJoinPath; + 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 627bc53..2e5d91d 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" diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 0429c76..93d7e79 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->fdwid = 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->fdwid == outer_rel->fdwid) + { + joinrel->fdwroutine = inner_rel->fdwroutine; + joinrel->fdwid = inner_rel->fdwid; + } + else + { + joinrel->fdwid = InvalidOid; + joinrel->fdwroutine = NULL; + } joinrel->fdw_private = NULL; joinrel->baserestrictinfo = NIL; joinrel->baserestrictcost.startup = 0; diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h index 0faad55..0f713fc 100644 --- a/src/include/foreign/fdwapi.h +++ b/src/include/foreign/fdwapi.h @@ -82,6 +82,24 @@ 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 ForeignScan *(*GetForeignJoinPlan_function) (PlannerInfo *root, + ForeignJoinPath *best_path, + List *tlist, + List *joinclauses, + List *otherclauses, + Plan *outer_plan, + Plan *inner_plan); + typedef void (*ExplainForeignScan_function) (ForeignScanState *node, struct ExplainState *es); @@ -157,6 +175,8 @@ typedef struct FdwRoutine extern FdwRoutine *GetFdwRoutine(Oid fdwhandler); extern FdwRoutine *GetFdwRoutineByServer(Oid server_id); extern FdwRoutine *GetFdwRoutineByRelId(Oid relid); +extern FdwRoutine *GetFdwRoutineByServerId(Oid serverid); +extern FdwRoutine *GetFdwRoutineByFdwId(Oid fdwid); extern FdwRoutine *GetFdwRoutineForRelation(Relation relation, bool makecopy); extern Oid GetForeignServerForRelation(Relation relation); extern bool IsImportableForeignTable(const char *tablename, 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/nodes.h b/src/include/nodes/nodes.h index bc71fea..160c0f6 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -224,6 +224,7 @@ typedef enum NodeTag T_NestPath, T_MergePath, T_HashPath, + T_ForeignJoinPath, T_TidPath, T_ForeignPath, T_CustomPath, diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index f9db9ce..6a949fa 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -461,6 +461,7 @@ typedef struct RelOptInfo PlannerInfo *subroot; /* if subquery */ List *subplan_params; /* if subquery */ /* use "struct FdwRoutine" to avoid including fdwapi.h here */ + Oid fdwid; /* if foreign table */ struct FdwRoutine *fdwroutine; /* if foreign table */ Oid fdw_server; /* if foreign table */ void *fdw_private; /* if foreign table */ @@ -1046,7 +1047,10 @@ typedef struct JoinPath * A nested-loop path needs no special fields. */ -typedef JoinPath NestPath; +typedef struct NestPath +{ + JoinPath jpath; +} NestPath; /* * A mergejoin path has these fields. @@ -1102,6 +1106,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);