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/doc/src/sgml/custom-scan.sgml b/doc/src/sgml/custom-scan.sgml new file mode 100644 index 0000000..1d103f5 --- /dev/null +++ b/doc/src/sgml/custom-scan.sgml @@ -0,0 +1,278 @@ + + + + Writing A Custom Scan Provider + + + custom scan provider + handler for + + + + Prior to query execution, the PostgreSQL planner constructs a plan tree + that usually consists of built-in plan nodes (eg: SeqScan, HashJoin, etc). + The custom-scan interface allows extensions to provide a custom-scan plan + that implements its own logic, in addition to the built-in nodes, to scan + a relation or join relations. Once a custom-scan node is chosen by planner, + callback functions associated with this custom-scan node shall be invoked + during query execution. Custom-scan provider is responsible for returning + equivalent result set as built-in logic would, but it is free to scan or + join the target relations according to its own logic. + This chapter explains how to write a custom-scan provider. + + + + The first thing custom-scan provider to do is adding alternative paths + to scan a relation (on the set_rel_pathlist_hook) or + to join relations (on the set_join_pathlist_hook). + It expects CustomPath node is added with estimated execution + cost and a set of callbacks defined at CustomPathMethods. + Both of hooks also give extensions enough information to construct + CustomPath node, like RelOptInfo of relations + to be scanned, joined or read as source of join. Custom-scan provider + is responsible to compute a reasonable cost estimation which is + comparable to built-in logics. + + + + Once a custom-path got chosen by planner, custom-scan provider has to + populate a plan node according to the CustomPath node. + At this moment, CustomScan is the only node type that allows + to implement custom-logic towards any CustomPath node. + The CustomScan structure has two special fields to keep + private information; custom_exprs and custom_private. + The custom_exprs intends to save a couple of expression trees + that shall be updated on setrefs.c and subselect.c. + On the other hands, custom_private is expected to save really + private information nobody will touch except for the custom-scan provider + itself. A plan-tree, which contains custom-scan node, can be duplicated + using copyObject(), so all the data structure stored within + these two fields must be safe to copyObject(). + + + + In case when extension implements its own logic to join relations, it looks + like a simple relation scan but on a pseudo materialized relation from + multiple source relations, from the standpoint of the core executor. + Custom-scan provider is expected to process relation join with its own + logic internally, then return a set of records according to the tuple + descriptor of the scan node. + CustomScan node that replaced a relations join is not + associated with a particular tangible relation, unlike simple scan case, + so extension needs to inform the core planner expected records type to be + fetched from this node. + What we should do here is, setting zero on the scanrelid and + a valid list of TargetEntry on the custom_ps_tlist + instead. These configuration informs the core planner this custom-scan + node is not associated with a particular physical table and expected + record type to be returned. + + + + Once a plan-tree is moved to the executor, it has to construct plan-state + objects according to the supplied plan-node. + Custom-scan is not an exception. Executor invokes a callback to populate + CustomScanState node, if CustomScan node gets + found in the supplied plan-tree. + It does not have fields to save private information unlike + CustomScan node, because custom-scan provider can allocate + larger object than the bare CustomScanState to store various + private execution state. + It looks like a relationship of ScanState structure towards + PlanState; that expands scan specific fields towards generic + plan-state. In addition, custom-scan provider can expand fields on demand. + Once a CustomScanState gets constructed, BeginCustomScan is invoked during + executor initialization; ExecCustomScan is repeatedly called during + execution (returning a TupleTableSlot with each fetched record), then + EndCustomScan is invoked on cleanup of the executor. + + + + Custom Scan Hooks and Callbacks + + + Custom Scan Hooks + + This hooks is invoked when the planner investigates the optimal way to + scan a particular relation. Extension can add alternative paths if it + can provide its own logic to scan towards the given scan and qualifiers. + +typedef void (*set_rel_pathlist_hook_type) (PlannerInfo *root, + RelOptInfo *rel, + Index rti, + RangeTblEntry *rte); +extern PGDLLIMPORT set_rel_pathlist_hook_type set_rel_pathlist_hook; + + + + + This hook is invoked when the planner investigates the optimal combination + of relations join. Extension can add alternative paths that replaces the + relation join with its own logic. + +typedef void (*set_join_pathlist_hook_type) (PlannerInfo *root, + RelOptInfo *joinrel, + RelOptInfo *outerrel, + RelOptInfo *innerrel, + List *restrictlist, + JoinType jointype, + SpecialJoinInfo *sjinfo, + SemiAntiJoinFactors *semifactors, + Relids param_source_rels, + Relids extra_lateral_rels); +extern PGDLLIMPORT set_join_pathlist_hook_type set_join_pathlist_hook; + + + + + + Custom Path Callbacks + + A CustomPathMethods table contains a set of callbacks related + to CustomPath node. The core backend invokes these callbacks + during query planning. + + + This callback is invoked when the core backend tries to populate + CustomScan node according to the supplied + CustomPath node. + Custom-scan provider is responsible to allocate a CustomScan + node and initialize each fields of them. + +Plan *(*PlanCustomPath) (PlannerInfo *root, + RelOptInfo *rel, + CustomPath *best_path, + List *tlist, + List *clauses); + + + + This optional callback will be invoked when nodeToString() + tries to create a text representation of CustomPath node. + A custom-scan provider can utilize this callback, if it wants to output + something additional. Note that expression nodes linked to + custom_private shall be transformed to text representation + by the core, so nothing to do by extension. + +void (*TextOutCustomPath) (StringInfo str, + const CustomPath *node); + + + + + + Custom Scan Callbacks + + A CustomScanMethods contains a set of callbacks related to + CustomScan node, then the core backend invokes these callbacks + during query planning and initialization of executor. + + + This callback shall be invoked when the core backend tries to populate + CustomScanState node according to the supplied + CustomScan node. The custom-scan provider is responsible to + allocate a CustomScanState (or its own data-type enhanced + from it), but no need to initialize the fields here, because + ExecInitCustomScan initializes the fields in + CustomScanState, then BeginCustomScan shall be + kicked on the end of executor initialization. + +Node *(*CreateCustomScanState) (CustomScan *cscan); + + + + This optional callback shall be invoked when nodeToString() + tries to make text representation of CustomScan node. + Custom-scan provider can utilize this callback, if it wants to output + something additional. Note that it is not allowed to expand the data + structure of CustomScan node, so we usually don't need to + implement this callback. + +void (*TextOutCustomScan) (StringInfo str, + const CustomScan *node); + + + + + + Custom Exec Callbacks + + A CustomExecMethods contains a set of callbacks related to + CustomScanState node, then the core backend invokes these + callbacks during query execution. + + + This callback allows a custom-scan provider to have final initialization + of the CustomScanState node. + The supplied CustomScanState node is partially initialized + according to either scanrelid or custom_ps_tlist + of CustomScan node. If the custom-scan provider wants to + apply additional initialization to the private fields, it can be done + by this callback. + +void (*BeginCustomScan) (CustomScanState *node, + EState *estate, + int eflags); + + + + This callback requires custom-scan provider to produce the next tuple + of the relation scan. If any tuples, it should set it on the + ps_ResultTupleSlot then returns the tuple slot. Elsewhere, + NULL or empty slot shall be returned to inform end of the + relation scan. + +TupleTableSlot *(*ExecCustomScan) (CustomScanState *node); + + + + This callback allows a custom-scan provider to cleanup the + CustomScanState node. If it holds any private (and not + released automatically) resources on the supplied node, it can release + these resources prior to the cleanup of the common portion. + +void (*EndCustomScan) (CustomScanState *node); + + + + This callback requires custom-scan provider to rewind the current scan + position to the head of relation. Custom-scan provider is expected to + reset its internal state to restart the relation scan again. + +void (*ReScanCustomScan) (CustomScanState *node); + + + + This optional callback requires custom-scan provider to save the current + scan position on its internal state. It shall be able to restore the + position using RestrPosCustomScan callback. It shall be never + called unless CUSTOMPATH_SUPPORT_MARK_RESTORE flag is set. + +void (*MarkPosCustomScan) (CustomScanState *node); + + + + This optional callback requires custom-scan provider to restore the + previous scan position that was saved by MarkPosCustomScan + callback. It shall be never called unless + CUSTOMPATH_SUPPORT_MARK_RESTORE flag is set. + +void (*RestrPosCustomScan) (CustomScanState *node); + + + + This optional callback allows custom-scan provider to output additional + information on EXPLAIN that involves custom-scan node. + Note that it can output common items; target-list, qualifiers, relation + to be scanned. So, it can be used when custom-scan provider wants to show + something others in addition to the items. + +void (*ExplainCustomScan) (CustomScanState *node, + List *ancestors, + ExplainState *es); + + + + + + diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index f03b72a..89fff77 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -93,6 +93,7 @@ + diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml index a648a4c..e378d69 100644 --- a/doc/src/sgml/postgres.sgml +++ b/doc/src/sgml/postgres.sgml @@ -242,6 +242,7 @@ &nls; &plhandler; &fdwhandler; + &custom-scan; &geqo; &indexam; &gist; diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 7cfc9bb..0b8de3f 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -1073,9 +1073,12 @@ ExplainNode(PlanState *planstate, List *ancestors, case T_ValuesScan: case T_CteScan: case T_WorkTableScan: + ExplainScanTarget((Scan *) plan, es); + break; case T_ForeignScan: case T_CustomScan: - ExplainScanTarget((Scan *) plan, es); + if (((Scan *) plan)->scanrelid > 0) + ExplainScanTarget((Scan *) plan, es); break; case T_IndexScan: { diff --git a/src/backend/executor/execScan.c b/src/backend/executor/execScan.c index 3f0d809..2f18a8a 100644 --- a/src/backend/executor/execScan.c +++ b/src/backend/executor/execScan.c @@ -251,6 +251,10 @@ ExecAssignScanProjectionInfo(ScanState *node) /* Vars in an index-only scan's tlist should be INDEX_VAR */ if (IsA(scan, IndexOnlyScan)) varno = INDEX_VAR; + /* Also foreign-/custom-scan on pseudo relation should be INDEX_VAR */ + else if (scan->scanrelid == 0 && + (IsA(scan, ForeignScan) || IsA(scan, CustomScan))) + varno = INDEX_VAR; else varno = scan->scanrelid; diff --git a/src/backend/executor/nodeCustom.c b/src/backend/executor/nodeCustom.c index b07932b..ca51333 100644 --- a/src/backend/executor/nodeCustom.c +++ b/src/backend/executor/nodeCustom.c @@ -23,6 +23,7 @@ CustomScanState * ExecInitCustomScan(CustomScan *cscan, EState *estate, int eflags) { CustomScanState *css; + Index scan_relid = cscan->scan.scanrelid; Relation scan_rel; /* populate a CustomScanState according to the CustomScan */ @@ -48,12 +49,31 @@ ExecInitCustomScan(CustomScan *cscan, EState *estate, int eflags) ExecInitScanTupleSlot(estate, &css->ss); ExecInitResultTupleSlot(estate, &css->ss.ps); - /* initialize scan relation */ - scan_rel = ExecOpenScanRelation(estate, cscan->scan.scanrelid, eflags); - css->ss.ss_currentRelation = scan_rel; - css->ss.ss_currentScanDesc = NULL; /* set by provider */ - ExecAssignScanType(&css->ss, RelationGetDescr(scan_rel)); - + /* + * open the base relation and acquire appropriate lock on it, then + * get the scan type from the relation descriptor, if this custom + * scan is on actual relations. + * + * on the other hands, custom-scan may scan on a pseudo relation; + * that is usually a result-set of relations join by external + * computing resource, or others. It has to get the scan type from + * the pseudo-scan target-list that should be assigned by custom-scan + * provider. + */ + if (scan_relid > 0) + { + scan_rel = ExecOpenScanRelation(estate, scan_relid, eflags); + css->ss.ss_currentRelation = scan_rel; + css->ss.ss_currentScanDesc = NULL; /* set by provider */ + ExecAssignScanType(&css->ss, RelationGetDescr(scan_rel)); + } + else + { + TupleDesc ps_tupdesc; + + ps_tupdesc = ExecTypeFromTL(cscan->custom_ps_tlist, false); + ExecAssignScanType(&css->ss, ps_tupdesc); + } css->ss.ps.ps_TupFromTlist = false; /* @@ -89,11 +109,11 @@ ExecEndCustomScan(CustomScanState *node) /* Clean out the tuple table */ ExecClearTuple(node->ss.ps.ps_ResultTupleSlot); - if (node->ss.ss_ScanTupleSlot) - ExecClearTuple(node->ss.ss_ScanTupleSlot); + ExecClearTuple(node->ss.ss_ScanTupleSlot); /* Close the heap relation */ - ExecCloseScanRelation(node->ss.ss_currentRelation); + if (node->ss.ss_currentRelation) + ExecCloseScanRelation(node->ss.ss_currentRelation); } void diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c index 7399053..f25eb6f 100644 --- a/src/backend/executor/nodeForeignscan.c +++ b/src/backend/executor/nodeForeignscan.c @@ -102,6 +102,7 @@ ForeignScanState * ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags) { ForeignScanState *scanstate; + Index scanrelid = node->scan.scanrelid; Relation currentRelation; FdwRoutine *fdwroutine; @@ -141,16 +142,28 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags) ExecInitScanTupleSlot(estate, &scanstate->ss); /* - * open the base relation and acquire appropriate lock on it. + * open the base relation and acquire appropriate lock on it, then + * get the scan type from the relation descriptor, if this foreign + * scan is on actual foreign-table. + * + * on the other hands, foreign-scan may scan on a pseudo relation; + * that is usually a result-set of remote relations join. It has + * to get the scan type from the pseudo-scan target-list that should + * be assigned by FDW driver. */ - currentRelation = ExecOpenScanRelation(estate, node->scan.scanrelid, eflags); - scanstate->ss.ss_currentRelation = currentRelation; + if (scanrelid > 0) + { + currentRelation = ExecOpenScanRelation(estate, scanrelid, eflags); + scanstate->ss.ss_currentRelation = currentRelation; + ExecAssignScanType(&scanstate->ss, RelationGetDescr(currentRelation)); + } + else + { + TupleDesc ps_tupdesc; - /* - * get the scan type from the relation descriptor. (XXX at some point we - * might want to let the FDW editorialize on the scan tupdesc.) - */ - ExecAssignScanType(&scanstate->ss, RelationGetDescr(currentRelation)); + ps_tupdesc = ExecTypeFromTL(node->fdw_ps_tlist, false); + ExecAssignScanType(&scanstate->ss, ps_tupdesc); + } /* * Initialize result tuple type and projection info. @@ -161,7 +174,7 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags) /* * Acquire function pointers from the FDW's handler, and init fdw_state. */ - fdwroutine = GetFdwRoutineForRelation(currentRelation, true); + fdwroutine = GetFdwRoutine(node->fdw_handler); scanstate->fdwroutine = fdwroutine; scanstate->fdw_state = NULL; @@ -193,7 +206,8 @@ ExecEndForeignScan(ForeignScanState *node) ExecClearTuple(node->ss.ss_ScanTupleSlot); /* close the relation. */ - ExecCloseScanRelation(node->ss.ss_currentRelation); + if (node->ss.ss_currentRelation) + ExecCloseScanRelation(node->ss.ss_currentRelation); } /* ---------------------------------------------------------------- diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c index cbe8b78..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. */ @@ -302,21 +325,16 @@ GetFdwRoutine(Oid fdwhandler) return routine; } - /* - * GetFdwRoutineByRelId - look up the handler of the foreign-data wrapper - * for the given foreign table, and retrieve its FdwRoutine struct. + * GetFdwHandlerByRelId - look up the handler of the foreign-data wrapper + * for the given foreign table */ -FdwRoutine * -GetFdwRoutineByRelId(Oid relid) +static Oid +GetFdwHandlerByRelId(Oid relid) { HeapTuple tp; - Form_pg_foreign_data_wrapper fdwform; - 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 +344,16 @@ GetFdwRoutineByRelId(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)) @@ -334,6 +362,16 @@ GetFdwRoutineByRelId(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)) @@ -350,7 +388,18 @@ GetFdwRoutineByRelId(Oid relid) ReleaseSysCache(tp); - /* And finally, call the handler function. */ + return fdwhandler; +} + +/* + * GetFdwRoutineByRelId - look up the handler of the foreign-data wrapper + * for the given foreign table, and retrieve its FdwRoutine struct. + */ +FdwRoutine * +GetFdwRoutineByRelId(Oid relid) +{ + Oid fdwhandler = GetFdwHandlerByRelId(relid); + return GetFdwRoutine(fdwhandler); } @@ -398,6 +447,16 @@ GetFdwRoutineForRelation(Relation relation, bool makecopy) return relation->rd_fdwroutine; } +/* + * GetFdwHandlerForRelation + * + * returns OID of FDW handler which is associated with the given relation. + */ +Oid +GetFdwHandlerForRelation(Relation relation) +{ + return GetFdwHandlerByRelId(RelationGetRelid(relation)); +} /* * IsImportableForeignTable - filter table names for IMPORT FOREIGN SCHEMA diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index f1a24f5..cb85468 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -590,7 +590,9 @@ _copyForeignScan(const ForeignScan *from) /* * copy remainder of node */ + COPY_SCALAR_FIELD(fdw_handler); COPY_NODE_FIELD(fdw_exprs); + COPY_NODE_FIELD(fdw_ps_tlist); COPY_NODE_FIELD(fdw_private); COPY_SCALAR_FIELD(fsSystemCol); @@ -615,6 +617,7 @@ _copyCustomScan(const CustomScan *from) */ COPY_SCALAR_FIELD(flags); COPY_NODE_FIELD(custom_exprs); + COPY_NODE_FIELD(custom_ps_tlist); COPY_NODE_FIELD(custom_private); /* diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index dd1278b..048db39 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -556,7 +556,9 @@ _outForeignScan(StringInfo str, const ForeignScan *node) _outScanInfo(str, (const Scan *) node); + WRITE_OID_FIELD(fdw_handler); WRITE_NODE_FIELD(fdw_exprs); + WRITE_NODE_FIELD(fdw_ps_tlist); WRITE_NODE_FIELD(fdw_private); WRITE_BOOL_FIELD(fsSystemCol); } @@ -570,6 +572,7 @@ _outCustomScan(StringInfo str, const CustomScan *node) WRITE_UINT_FIELD(flags); WRITE_NODE_FIELD(custom_exprs); + WRITE_NODE_FIELD(custom_ps_tlist); WRITE_NODE_FIELD(custom_private); appendStringInfoString(str, " :methods "); _outToken(str, node->methods->CustomName); @@ -1700,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"); @@ -1798,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); @@ -3122,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 e6aa21c..04e59e6 100644 --- a/src/backend/optimizer/path/joinpath.c +++ b/src/backend/optimizer/path/joinpath.c @@ -17,10 +17,13 @@ #include #include "executor/executor.h" +#include "foreign/fdwapi.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" +/* Hook for plugins to get control in add_paths_to_joinrel() */ +set_join_pathlist_hook_type set_join_pathlist_hook = NULL; #define PATH_PARAM_BY_REL(path, rel) \ ((path)->param_info && bms_overlap(PATH_REQ_OUTER(path), (rel)->relids)) @@ -50,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, @@ -207,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) @@ -217,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 @@ -232,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). * @@ -250,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. */ @@ -259,6 +283,19 @@ add_paths_to_joinrel(PlannerInfo *root, restrictlist, jointype, sjinfo, &semifactors, param_source_rels, extra_lateral_rels); + + /* + * 5. Consider paths added by FDW drivers or custom-scan providers, in + * addition to built-in paths. + * + * XXX - In case of FDW, we may be able to omit invocation if joinrel's + * fdwhandler (set only if both relations are managed by same FDW server). + */ + if (set_join_pathlist_hook) + set_join_pathlist_hook(root, joinrel, outerrel, innerrel, + restrictlist, jointype, + sjinfo, &semifactors, + param_source_rels, extra_lateral_rels); } /* diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 655be81..d20fb50 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -83,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, @@ -241,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: @@ -611,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; @@ -625,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; @@ -1958,16 +1977,26 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path, ForeignScan *scan_plan; RelOptInfo *rel = best_path->path.parent; Index scan_relid = rel->relid; - RangeTblEntry *rte; + Oid rel_oid = InvalidOid; Bitmapset *attrs_used = NULL; ListCell *lc; int i; - /* it should be a base rel... */ - Assert(scan_relid > 0); - Assert(rel->rtekind == RTE_RELATION); - rte = planner_rt_fetch(scan_relid, root); - Assert(rte->rtekind == RTE_RELATION); + /* + * Fetch relation-id, if this foreign-scan node actuall scans on + * a particular real relation. Elsewhere, InvalidOid shall be + * informed to the FDW driver. + */ + if (scan_relid > 0) + { + RangeTblEntry *rte; + + Assert(rel->rtekind == RTE_RELATION); + rte = planner_rt_fetch(scan_relid, root); + Assert(rte->rtekind == RTE_RELATION); + rel_oid = rte->relid; + } + Assert(rel->fdwroutine != NULL); /* * Sort clauses into best execution order. We do this first since the FDW @@ -1982,13 +2011,16 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path, * has selected some join clauses for remote use but also wants them * rechecked locally). */ - scan_plan = rel->fdwroutine->GetForeignPlan(root, rel, rte->relid, + scan_plan = rel->fdwroutine->GetForeignPlan(root, rel, rel_oid, best_path, tlist, scan_clauses); /* Copy cost data from Path to Plan; no need to make FDW do this */ copy_path_costsize(&scan_plan->scan.plan, &best_path->path); + /* Track FDW server-id; no need to make FDW do this */ + scan_plan->fdw_handler = rel->fdw_handler; + /* * Replace any outer-relation variables with nestloop params in the qual * and fdw_exprs expressions. We do this last so that the FDW doesn't @@ -2052,12 +2084,6 @@ create_customscan_plan(PlannerInfo *root, CustomPath *best_path, RelOptInfo *rel = best_path->path.parent; /* - * Right now, all we can support is CustomScan node which is associated - * with a particular base relation to be scanned. - */ - Assert(rel && rel->reloptkind == RELOPT_BASEREL); - - /* * Sort clauses into the best execution order, although custom-scan * provider can reorder them again. */ @@ -2108,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; @@ -2127,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); @@ -2140,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); @@ -2152,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) @@ -2189,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; } @@ -2199,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; @@ -2494,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; @@ -2616,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/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 7703946..d567c49 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -569,6 +569,36 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) { ForeignScan *splan = (ForeignScan *) plan; + if (splan->fdw_ps_tlist != NIL) + { + indexed_tlist *pscan_itlist = + build_tlist_index(splan->fdw_ps_tlist); + + Assert(splan->scan.scanrelid == 0); + + splan->scan.plan.targetlist = (List *) + fix_upper_expr(root, + (Node *) splan->scan.plan.targetlist, + pscan_itlist, + INDEX_VAR, + rtoffset); + splan->scan.plan.qual = (List *) + fix_upper_expr(root, + (Node *) splan->scan.plan.qual, + pscan_itlist, + INDEX_VAR, + rtoffset); + splan->fdw_exprs = (List *) + fix_upper_expr(root, + (Node *) splan->fdw_exprs, + pscan_itlist, + INDEX_VAR, + rtoffset); + splan->fdw_ps_tlist = + fix_scan_list(root, splan->fdw_ps_tlist, rtoffset); + pfree(pscan_itlist); + break; + } splan->scan.scanrelid += rtoffset; splan->scan.plan.targetlist = fix_scan_list(root, splan->scan.plan.targetlist, rtoffset); @@ -583,6 +613,36 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) { CustomScan *splan = (CustomScan *) plan; + if (splan->custom_ps_tlist != NIL) + { + indexed_tlist *pscan_itlist = + build_tlist_index(splan->custom_ps_tlist); + + Assert(splan->scan.scanrelid == 0); + + splan->scan.plan.targetlist = (List *) + fix_upper_expr(root, + (Node *) splan->scan.plan.targetlist, + pscan_itlist, + INDEX_VAR, + rtoffset); + splan->scan.plan.qual = (List *) + fix_upper_expr(root, + (Node *) splan->scan.plan.qual, + pscan_itlist, + INDEX_VAR, + rtoffset); + splan->custom_exprs = (List *) + fix_upper_expr(root, + (Node *) splan->custom_exprs, + pscan_itlist, + INDEX_VAR, + rtoffset); + splan->custom_ps_tlist = + fix_scan_list(root, splan->custom_ps_tlist, rtoffset); + pfree(pscan_itlist); + break; + } splan->scan.scanrelid += rtoffset; splan->scan.plan.targetlist = fix_scan_list(root, splan->scan.plan.targetlist, rtoffset); diff --git a/src/backend/optimizer/util/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 fb7db6d..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" @@ -378,10 +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->fdw_handler = GetFdwHandlerForRelation(relation); rel->fdwroutine = GetFdwRoutineForRelation(relation, true); + } else + { + rel->fdw_handler = InvalidOid; rel->fdwroutine = NULL; - + } heap_close(relation, NoLock); /* diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 8cfbea0..667ae1b 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -14,6 +14,7 @@ */ #include "postgres.h" +#include "foreign/fdwapi.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" @@ -121,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; @@ -383,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; @@ -427,6 +439,18 @@ build_join_rel(PlannerInfo *root, sjinfo, restrictlist); /* + * Set FDW handler and routine if both outer and inner relation + * are managed by same FDW driver. + */ + if (OidIsValid(outer_rel->fdw_handler) && + OidIsValid(inner_rel->fdw_handler) && + outer_rel->fdw_handler == inner_rel->fdw_handler) + { + joinrel->fdw_handler = outer_rel->fdw_handler; + joinrel->fdwroutine = GetFdwRoutine(joinrel->fdw_handler); + } + + /* * Add the joinrel to the query's joinrel list, and store it into the * auxiliary hashtable if there is one. NB: GEQO requires us to append * the new joinrel to the end of the list! diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index c1d860c..eb9eaf0 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -3842,6 +3842,10 @@ set_deparse_planstate(deparse_namespace *dpns, PlanState *ps) /* index_tlist is set only if it's an IndexOnlyScan */ if (IsA(ps->plan, IndexOnlyScan)) dpns->index_tlist = ((IndexOnlyScan *) ps->plan)->indextlist; + else if (IsA(ps->plan, ForeignScan)) + dpns->index_tlist = ((ForeignScan *) ps->plan)->fdw_ps_tlist; + else if (IsA(ps->plan, CustomScan)) + dpns->index_tlist = ((CustomScan *) ps->plan)->custom_ps_tlist; else dpns->index_tlist = NIL; } diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h index 1d76841..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,13 +168,21 @@ 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, ImportForeignSchemaStmt *stmt); 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/plannodes.h b/src/include/nodes/plannodes.h index 316c9ce..6717c6d 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -470,7 +470,13 @@ typedef struct WorkTableScan * fdw_exprs and fdw_private are both under the control of the foreign-data * wrapper, but fdw_exprs is presumed to contain expression trees and will * be post-processed accordingly by the planner; fdw_private won't be. - * Note that everything in both lists must be copiable by copyObject(). + * An optional fdw_ps_tlist is used to map a reference to an attribute of + * underlying relation(s) on a pair of INDEX_VAR and alternative varattno. + * It looks like a scan on pseudo relation that is usually result of + * relations join on remote data source, and FDW driver is responsible to + * set expected target list for this. If FDW returns records as foreign- + * table definition, just put NIL here. + * Note that everything in above lists must be copiable by copyObject(). * One way to store an arbitrary blob of bytes is to represent it as a bytea * Const. Usually, though, you'll be better off choosing a representation * that can be dumped usefully by nodeToString(). @@ -479,7 +485,9 @@ typedef struct WorkTableScan typedef struct ForeignScan { Scan scan; + Oid fdw_handler; /* OID of FDW handler */ List *fdw_exprs; /* expressions that FDW may evaluate */ + List *fdw_ps_tlist; /* optional pseudo-scan tlist for FDW */ List *fdw_private; /* private data for FDW */ bool fsSystemCol; /* true if any "system column" is needed */ } ForeignScan; @@ -487,10 +495,11 @@ typedef struct ForeignScan /* ---------------- * CustomScan node * - * The comments for ForeignScan's fdw_exprs and fdw_private fields apply - * equally to custom_exprs and custom_private. Note that since Plan trees - * can be copied, custom scan providers *must* fit all plan data they need - * into those fields; embedding CustomScan in a larger struct will not work. + * The comments for ForeignScan's fdw_exprs, fdw_varmap and fdw_private fields + * apply equally to custom_exprs, custom_ps_tlist and custom_private. + * Note that since Plan trees can be copied, custom scan providers *must* + * fit all plan data they need into those fields; embedding CustomScan in + * a larger struct will not work. * ---------------- */ struct CustomScan; @@ -511,6 +520,7 @@ typedef struct CustomScan Scan scan; uint32 flags; /* mask of CUSTOMPATH_* flags, see relation.h */ List *custom_exprs; /* expressions that custom code may evaluate */ + List *custom_ps_tlist;/* optional pseudo-scan target list */ List *custom_private; /* private data for custom code */ const CustomScanMethods *methods; } CustomScan; diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 6845a40..9914d1d 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -366,6 +366,7 @@ typedef struct PlannerInfo * subroot - PlannerInfo for subquery (NULL if it's not a subquery) * subplan_params - list of PlannerParamItems to be passed to subquery * fdwroutine - function hooks for FDW, if foreign table (else NULL) + * fdw_handler - OID of FDW handler, if foreign table (else InvalidOid) * fdw_private - private state for FDW, if foreign table (else NULL) * * Note: for a subquery, tuples, subplan, subroot are not set immediately @@ -461,6 +462,7 @@ typedef struct RelOptInfo List *subplan_params; /* if subquery */ /* use "struct FdwRoutine" to avoid including fdwapi.h here */ struct FdwRoutine *fdwroutine; /* if foreign table */ + Oid fdw_handler; /* if foreign table */ void *fdw_private; /* if foreign table */ /* used by various scans and joins: */ @@ -1044,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. @@ -1100,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/paths.h b/src/include/optimizer/paths.h index 6cad92e..c42c69d 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -30,6 +30,19 @@ typedef void (*set_rel_pathlist_hook_type) (PlannerInfo *root, RangeTblEntry *rte); extern PGDLLIMPORT set_rel_pathlist_hook_type set_rel_pathlist_hook; +/* Hook for plugins to get control in add_paths_to_joinrel() */ +typedef void (*set_join_pathlist_hook_type) (PlannerInfo *root, + RelOptInfo *joinrel, + RelOptInfo *outerrel, + RelOptInfo *innerrel, + List *restrictlist, + JoinType jointype, + SpecialJoinInfo *sjinfo, + SemiAntiJoinFactors *semifactors, + Relids param_source_rels, + Relids extra_lateral_rels); +extern PGDLLIMPORT set_join_pathlist_hook_type set_join_pathlist_hook; + /* Hook for plugins to replace standard_join_search() */ typedef RelOptInfo *(*join_search_hook_type) (PlannerInfo *root, int levels_needed,