diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c index 59cb053..6795e5f 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 d76e739..0a645b6 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -48,7 +48,8 @@ PG_MODULE_MAGIC; /* * FDW-specific planner information kept in RelOptInfo.fdw_private for a - * foreign table. This information is collected by postgresGetForeignRelSize. + * foreign table or foreign join. This information is collected by + * postgresGetForeignRelSize, or calculated from join source relations. */ typedef struct PgFdwRelationInfo { @@ -288,6 +289,22 @@ 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); +static ForeignScan *postgresGetForeignJoinPlan(PlannerInfo *root, + ForeignJoinPath *best_path, + List *tlist, + List *joinclauses, + List *otherclauses, + Plan *outer_plan, + Plan *inner_plan); /* * Helper functions @@ -368,6 +385,10 @@ 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->GetForeignJoinPlan = postgresGetForeignJoinPlan; + PG_RETURN_POINTER(routine); } @@ -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 @@ -906,13 +932,23 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags) * Identify which user to do the remote access as. This should match what * ExecCheckRTEPerms() does. */ - rte = rt_fetch(fsplan->scan.scanrelid, estate->es_range_table); - userid = rte->checkAsUser ? rte->checkAsUser : GetUserId(); + if (fsplan->scan.scanrelid > 0) + { + rte = rt_fetch(fsplan->scan.scanrelid, estate->es_range_table); + userid = rte->checkAsUser ? rte->checkAsUser : GetUserId(); + + fsstate->rel = node->ss.ss_currentRelation; + table = GetForeignTable(RelationGetRelid(fsstate->rel)); + server = GetForeignServer(table->serverid); + } + else + { + /* XXX how can we determine userid to use for join cases? */ + userid = GetCurrentRoleId(); + server = GetForeignServer(16409); + } /* Get info about foreign table. */ - fsstate->rel = node->ss.ss_currentRelation; - table = GetForeignTable(RelationGetRelid(fsstate->rel)); - server = GetForeignServer(table->serverid); user = GetUserMapping(userid, server->serverid); /* @@ -944,7 +980,16 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags) ALLOCSET_SMALL_MAXSIZE); /* Get info we'll need for input data conversion. */ - fsstate->attinmeta = TupleDescGetAttInMetadata(RelationGetDescr(fsstate->rel)); + if (fsplan->scan.scanrelid > 0) + fsstate->attinmeta = + TupleDescGetAttInMetadata(RelationGetDescr(fsstate->rel)); + else + { + TupleDesc ps_tupdesc; + + ps_tupdesc = ExecTypeFromTL(fsplan->fdw_ps_tlist, false); + fsstate->attinmeta = TupleDescGetAttInMetadata(ps_tupdesc); + } /* Prepare for output conversion of parameters used in remote query. */ numParams = list_length(fsplan->fdw_exprs); @@ -1725,10 +1770,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 +1790,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 +2887,233 @@ postgresImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid) } /* + * Construct PgFdwRelationInfo from two join sources + */ +static PgFdwRelationInfo * +merge_fpinfo(PgFdwRelationInfo *fpinfo_o, PgFdwRelationInfo *fpinfo_i) +{ + PgFdwRelationInfo *fpinfo; + + fpinfo = (PgFdwRelationInfo *) palloc0(sizeof(PgFdwRelationInfo)); + fpinfo->remote_conds = + list_concat(fpinfo_o->remote_conds, fpinfo_i->remote_conds); + fpinfo->local_conds = + list_concat(fpinfo_o->local_conds, fpinfo_i->local_conds); + + fpinfo->attrs_used = NULL; /* Use fdw_ps_tlist */ + fpinfo->local_conds_cost.startup = + fpinfo_o->local_conds_cost.startup + fpinfo_i->local_conds_cost.startup; + fpinfo->local_conds_cost.per_tuple = + fpinfo_o->local_conds_cost.per_tuple + fpinfo_i->local_conds_cost.per_tuple; + fpinfo->local_conds_sel = + fpinfo_o->local_conds_sel * fpinfo_i->local_conds_sel; + /* XXX we should use join selectivity and join type */ + fpinfo->rows = Min(fpinfo_o->rows, fpinfo_i->rows); + /* XXX we should consider only columns in fdw_ps_tlist */ + fpinfo->width = fpinfo_o->width + fpinfo_i->width; + /* XXX we should estimate better costs */ + + fpinfo->use_remote_estimate = false; /* Never use in join case */ + fpinfo->fdw_startup_cost = fpinfo_o->fdw_startup_cost; + fpinfo->fdw_tuple_cost = fpinfo_o->fdw_tuple_cost; + + fpinfo->startup_cost = fpinfo->fdw_startup_cost; + fpinfo->total_cost = + fpinfo->startup_cost + fpinfo->fdw_tuple_cost * fpinfo->rows; + + fpinfo->table = NULL; /* always NULL in join case */ + fpinfo->server = fpinfo_o->server; + fpinfo->user = fpinfo_o->user ? fpinfo_o->user : fpinfo_i->user; + + return fpinfo; +} + +/* + * 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) +{ + ForeignJoinPath *joinpath; + Path *path_o = outerrel->cheapest_total_path; + Path *path_i = innerrel->cheapest_total_path; + PgFdwRelationInfo *fpinfo_o; + PgFdwRelationInfo *fpinfo_i; + Relids required_outer; + + /* Skip considering reversed join combination */ + elog(DEBUG1, "%s() outer: %d, inner: %d", + __func__, outerrel->relid, innerrel->relid); + if (outerrel->relid < innerrel->relid) + 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; + + /* + * Both relations in the join must belong to same server, and have same + * checkAsUser to use one connection to execute SQL for the join. + */ + if (IsA(path_o, ForeignPath)) + fpinfo_o = ((ForeignPath *) path_o)->path.parent->fdw_private; + else if (IsA(path_o, ForeignJoinPath)) + fpinfo_o = ((ForeignJoinPath *) path_o)->jpath.path.parent->fdw_private; + else + fpinfo_o = NULL; + Assert(fpinfo_o); + if (IsA(path_i, ForeignPath)) + fpinfo_i = ((ForeignPath *) path_i)->path.parent->fdw_private; + else if (IsA(path_i, ForeignJoinPath)) + fpinfo_i = ((ForeignJoinPath *) path_i)->jpath.path.parent->fdw_private; + else + fpinfo_i = NULL; + Assert(fpinfo_i); + + /* Servers should match */ + if (fpinfo_o->server->serverid != fpinfo_i->server->serverid) + return; + + /* Construct fpinfo for the join relation */ + joinrel->fdw_private = merge_fpinfo(fpinfo_o, fpinfo_i); + + /* + * 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(path_o, path_i); + joinpath = create_foreignjoin_path(root, + joinrel, + jointype, + sjinfo, + semifactors, + path_o, + path_i, + 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 */ +} + +/* + * postgresGetForeignJoinPlan + * Create ForeignJoin plan node from given ForeignJoinPath. + * + */ +static ForeignScan * +postgresGetForeignJoinPlan(PlannerInfo *root, + ForeignJoinPath *best_path, + List *tlist, + List *joinclauses, + List *otherclauses, + Plan *outer_plan, + Plan *inner_plan) +{ + ForeignScan *join_plan; + List *params_list = NIL; + List *fdw_private = NIL; + List *retrieved_attrs = NIL; + Relids relids; + StringInfoData sql; + ForeignPath *path_o; + ForeignPath *path_i; + List *retrieved_attrs_o = NIL; + List *retrieved_attrs_i = NIL; + PgFdwRelationInfo *fpinfo_o; + PgFdwRelationInfo *fpinfo_i; + PgFdwDeparseRel dr_o; + PgFdwDeparseRel dr_i; + + /* + * At the moment we support only joins between foreign tables. This + * limitation will be relaxed in future releases. + */ + Assert(IsA(outer_plan, ForeignScan)); + Assert(IsA(inner_plan, ForeignScan)); + + /* + * Retrieve Path and PgFdwRelationInfo of underlying ForeignScan to reuse + * various information cumputed in ForeignScan planning. + */ + path_o = (ForeignPath *) best_path->jpath.outerjoinpath; + fpinfo_o = path_o->path.parent->fdw_private; + path_i = (ForeignPath *) best_path->jpath.innerjoinpath; + fpinfo_i = path_i->path.parent->fdw_private; + + /* + * Construcr deparse information for two relations. + */ + dr_o.baserel = path_o->path.parent; + dr_o.jointype = JOIN_INNER; + dr_o.joinclauses = NIL; + dr_o.attrs_used = fpinfo_o->attrs_used; + dr_o.retrieved_attrs = &retrieved_attrs_o; + dr_i.baserel = path_i->path.parent; + dr_i.jointype = best_path->jpath.jointype; + dr_i.joinclauses = joinclauses; + dr_i.attrs_used = fpinfo_i->attrs_used; + dr_i.retrieved_attrs = &retrieved_attrs_i; + + relids = NULL; + relids = bms_add_member(relids, dr_o.baserel->relid); + relids = bms_add_member(relids, dr_i.baserel->relid); + + initStringInfo(&sql); + deparseSelectSql(&sql, root, list_make2(&dr_o, &dr_i)); + if (fpinfo_o->remote_conds) + appendWhereClause(&sql, root, relids, fpinfo_o->remote_conds, true, + ¶ms_list); + if (fpinfo_i->remote_conds) + appendWhereClause(&sql, root, relids, fpinfo_i->remote_conds, + (fpinfo_o->remote_conds == NULL), ¶ms_list); + + /* + * Different from ForeignScan, we store retrieved_attrs as a list of lists. + * This allows subsequent processing to distinguish which relation is the + * source. + */ + retrieved_attrs = list_make2(list_copy(*dr_o.retrieved_attrs), + list_copy(*dr_i.retrieved_attrs)); + fdw_private = list_make2(makeString(sql.data), retrieved_attrs); + elog(DEBUG1, "sql: %s", sql.data); + elog(DEBUG1, "retrieved_attrs: %s", nodeToString(retrieved_attrs)); + + join_plan = make_foreignscan(tlist, + NIL, + 0, + params_list, + fdw_private); + return join_plan; +} + +/* * 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 950c6f7..c71bf21 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 df69a95..d77eeea 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. */ @@ -310,12 +333,8 @@ static Oid GetFdwHandlerByRelId(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)); @@ -325,6 +344,16 @@ GetFdwHandlerByRelId(Oid relid) serverid = tableform->ftserver; ReleaseSysCache(tp); + return GetFdwRoutineByServerId(serverid); +} + +FdwRoutine * +GetFdwRoutineByServerId(Oid serverid) +{ + HeapTuple tp; + Form_pg_foreign_server serverform; + Oid fdwid; + /* Get foreign-data wrapper OID for the server. */ tp = SearchSysCache1(FOREIGNSERVEROID, ObjectIdGetDatum(serverid)); if (!HeapTupleIsValid(tp)) @@ -333,6 +362,16 @@ GetFdwHandlerByRelId(Oid relid) 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 c4a06fc..048db39 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -1703,6 +1703,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"); @@ -1801,6 +1811,7 @@ _outRelOptInfo(StringInfo str, const RelOptInfo *node) WRITE_NODE_FIELD(subplan); WRITE_NODE_FIELD(subroot); WRITE_NODE_FIELD(subplan_params); + WRITE_OID_FIELD(fdw_handler); /* we don't try to print fdwroutine or fdw_private */ WRITE_NODE_FIELD(baserestrictinfo); WRITE_NODE_FIELD(joininfo); @@ -3125,6 +3136,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 020558b..a8506fc 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 5a24efa..04e59e6 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, @@ -209,7 +209,29 @@ 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, and share same server. Besides it, + * checkAsUser of all relations in the join must match. These limitations + * ensure that + * This is done preceding to any local join consideration because + * foreign join 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) @@ -219,7 +241,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 @@ -234,7 +256,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). * @@ -252,7 +274,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 06bea4d..d20fb50 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -44,6 +44,7 @@ #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); @@ -82,11 +83,14 @@ 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 ForeignScan *create_foreignjoin_plan(PlannerInfo *root, + ForeignJoinPath *best_path, 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, @@ -219,7 +223,7 @@ create_plan(PlannerInfo *root, Path *best_path) * create_plan_recurse * Recursive guts of create_plan(). */ -Plan * +static Plan * create_plan_recurse(PlannerInfo *root, Path *best_path) { Plan *plan; @@ -240,6 +244,7 @@ create_plan_recurse(PlannerInfo *root, Path *best_path) case T_CustomScan: plan = create_scan_plan(root, best_path); break; + case T_ForeignJoinPath: case T_HashJoin: case T_MergeJoin: case T_NestLoop: @@ -610,6 +615,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; @@ -624,27 +630,41 @@ 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_ForeignJoinPath: + plan = (Plan *) create_foreignjoin_plan(root, + (ForeignJoinPath *) best_path, + tlist, + outer_plan, + inner_plan); + break; 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; @@ -2114,12 +2134,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; @@ -2133,7 +2153,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); @@ -2146,7 +2166,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); @@ -2158,7 +2178,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) @@ -2195,9 +2215,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; } @@ -2205,10 +2225,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; @@ -2500,10 +2520,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; @@ -2622,6 +2642,53 @@ create_hashjoin_plan(PlannerInfo *root, return join_plan; } +/* + * Unlike other join paths, ForeignJoinPath is transformed into ForiegnScan + * plan node. + */ +static ForeignScan * +create_foreignjoin_plan(PlannerInfo *root, + ForeignJoinPath *best_path, + List *tlist, + Plan *outer_plan, + Plan *inner_plan) +{ + ForeignScan *join_plan; + List *joinrestrictclauses = best_path->jpath.joinrestrictinfo; + List *joinclauses; + List *otherclauses; + + /* Sort join qual clauses into best execution order */ + joinrestrictclauses = order_qual_clauses(root, joinrestrictclauses); + + /* Get the join qual clauses (in plain expression form) */ + /* Any pseudoconstant clauses are ignored here */ + if (IS_OUTER_JOIN(best_path->jpath.jointype)) + { + extract_actual_join_clauses(joinrestrictclauses, + &joinclauses, &otherclauses); + } + else + { + /* We can treat all clauses alike for an inner join */ + joinclauses = extract_actual_clauses(joinrestrictclauses, false); + otherclauses = NIL; + } + + /* Call FDW handler */ + { + RelOptInfo *rel = best_path->jpath.path.parent; + + Assert(rel->fdwroutine); + join_plan = rel->fdwroutine->GetForeignJoinPlan(root, best_path, + tlist, joinclauses, + otherclauses, + outer_plan, inner_plan); + join_plan->fdw_handler = rel->fdw_handler; + } + + return join_plan; +} /***************************************************************************** * diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index 1395a21..d6434bf 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 a4a35c3..57763d4 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 ca71093..667ae1b 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -122,6 +122,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind) rel->subplan = NULL; rel->subroot = NULL; rel->subplan_params = NIL; + rel->fdw_handler = InvalidOid; rel->fdwroutine = NULL; rel->fdw_private = NULL; rel->baserestrictinfo = NIL; @@ -384,7 +385,17 @@ build_join_rel(PlannerInfo *root, joinrel->subplan = NULL; joinrel->subroot = NULL; joinrel->subplan_params = NIL; - joinrel->fdwroutine = NULL; + /* propagate common FDW information up to join relation */ + if (inner_rel->fdw_handler == outer_rel->fdw_handler) + { + joinrel->fdwroutine = inner_rel->fdwroutine; + joinrel->fdw_handler = inner_rel->fdw_handler; + } + else + { + joinrel->fdw_handler = 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 b494ff2..b1f8532 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); @@ -150,12 +168,19 @@ typedef struct FdwRoutine /* Support functions for IMPORT FOREIGN SCHEMA */ ImportForeignSchema_function ImportForeignSchema; + + /* Support functions for join push-down */ + GetForeignJoinPath_function GetForeignJoinPath; + GetForeignJoinPlan_function GetForeignJoinPlan; + } FdwRoutine; /* Functions in foreign/foreign.c */ extern FdwRoutine *GetFdwRoutine(Oid fdwhandler); extern FdwRoutine *GetFdwRoutineByRelId(Oid relid); +extern FdwRoutine * GetFdwRoutineByServerId(Oid serverid); +extern FdwRoutine * GetFdwRoutineByFdwId(Oid fdwid); extern FdwRoutine *GetFdwRoutineForRelation(Relation relation, bool makecopy); extern Oid GetFdwHandlerForRelation(Relation relation); extern bool IsImportableForeignTable(const char *tablename, diff --git a/src/include/foreign/foreign.h b/src/include/foreign/foreign.h index 9c737b4..35acae7 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 97ef0fc..0f7a15d 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 9ef0b56..9914d1d 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -1046,7 +1046,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 +1105,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 9923f0e..d4b6498 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 e66eaa5..082f7d7 100644 --- a/src/include/optimizer/planmain.h +++ b/src/include/optimizer/planmain.h @@ -41,7 +41,6 @@ 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,