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,