diff --git a/contrib/pgsql_fdw/deparse.c b/contrib/pgsql_fdw/deparse.c
index b5f6c79..d24b629 100644
*** a/contrib/pgsql_fdw/deparse.c
--- b/contrib/pgsql_fdw/deparse.c
*************** is_proc_remotely_executable(Oid procid)
*** 362,364 ****
--- 362,538 ----
return true;
}
+ /*
+ * Append WHERE clause elements to buf.
+ */
+ static void
+ append_where_clause(StringInfo buf, List *context, Path *path, bool *first)
+ {
+ List *restrictinfo;
+ ListCell *lc;
+ Path *outer_path = NULL;
+ Path *inner_path = NULL;
+
+ /* Determine that which restrictinfo is used. */
+ if (IsA(path, ForeignPath))
+ {
+ restrictinfo = path->parent->baserestrictinfo;
+ }
+ else if (IsA(path, ForeignJoinPath))
+ {
+ JoinPath *joinpath = (JoinPath *) path;
+ restrictinfo = joinpath->joinrestrictinfo;
+ outer_path = joinpath->outerjoinpath;
+ inner_path = joinpath->innerjoinpath;
+ }
+
+ /* Deparse restrict expressions. */
+ foreach(lc, restrictinfo)
+ {
+ RestrictInfo *ri = (RestrictInfo *) lfirst(lc);
+
+ if (!*first)
+ appendStringInfo(buf, " AND ");
+ appendStringInfo(buf, "%s",
+ deparse_expression((Node *) ri->clause, context, true, false));
+ *first = false;
+ }
+
+ /* Use children's restrictinfo recursively, if any. */
+ if (outer_path != NULL)
+ append_where_clause(buf, context, outer_path, first);
+ if (inner_path != NULL)
+ append_where_clause(buf, context, inner_path, first);
+ }
+
+ /*
+ * Store qualified relation name into result.
+ */
+ static void
+ get_qualified_relname(Oid relid, StringInfo result)
+ {
+ ForeignTable *ft; /* foreign table to be scanned */
+ const char *nspname = NULL; /* plain namespace name */
+ const char *relname = NULL; /* plain relation name */
+ const char *q_nspname; /* quoted namespace name */
+ const char *q_relname; /* quoted relation name */
+
+ ft = GetForeignTable(relid);
+ if (ft->options != NIL)
+ {
+ ListCell *lc;
+
+ foreach (lc, ft->options)
+ {
+ DefElem *opt = lfirst(lc);
+ if (strcmp(opt->defname, "nspname") == 0)
+ nspname = strVal(opt->arg);
+ else if (strcmp(opt->defname, "relname") == 0)
+ relname = strVal(opt->arg);
+ }
+ }
+ if (nspname == NULL)
+ nspname = get_namespace_name(get_rel_namespace(relid));
+ if (relname == NULL)
+ relname = get_rel_name(relid);
+ q_nspname = quote_identifier(nspname);
+ q_relname = quote_identifier(relname);
+ appendStringInfo(result, "%s.%s", q_nspname, q_relname);
+ }
+
+ /*
+ * Deparse join representation into SQL statement which suits for remote
+ * PostgreSQL server. See also deparseSql().
+ */
+ char *
+ deparseJoinSql(Oid serverid,
+ PlannerInfo *root,
+ RelOptInfo *joinrel,
+ JoinType jointype,
+ SpecialJoinInfo *sjinfo,
+ Path *outer_path,
+ Path *inner_path,
+ List *restrict_clauses,
+ List *pathkeys)
+ {
+ int i;
+ List *rtables = NIL;
+ List *context;
+ StringInfoData sql;
+ bool first;
+ ListCell *lc;
+ Bitmapset *bms;
+
+ /*
+ * First of all, check that this join can be pushed down.
+ *
+ * There are some redundancy such as duplicated attribute loop, but this
+ * is necessary to avoid memory leak.
+ */
+ foreach(lc, joinrel->reltargetlist)
+ {
+ Var *var = (Var *) lfirst(lc);
+ if (var->varattno < 0)
+ return NULL;
+ }
+
+ /* Create context used for deparsing this join. */
+ for (i = 1; i < root->simple_rel_array_size; i++)
+ {
+ rtables = lappend(rtables, copyObject(root->simple_rte_array[i]));
+ }
+ context = deparse_context_for_rtelist(rtables);
+
+ /* Initialize buffer. */
+ initStringInfo(&sql);
+
+ /* deparse SELECT clause */
+ /* TODO: replace unused references with NULL */
+ appendStringInfo(&sql, "SELECT ");
+ first = true;
+ foreach(lc, joinrel->reltargetlist)
+ {
+ Var *var = (Var *) lfirst(lc);
+ appendStringInfo(&sql, "%s%s", first ? "" : ", ",
+ deparse_expression((Node *) var, context, true, false));
+ first = false;
+ }
+
+ /* deparse FROM clause */
+ appendStringInfo(&sql, "\nFROM ");
+ bms = bms_copy(joinrel->relids);
+ first = true;
+ while (!bms_is_empty(bms))
+ {
+ int relid = bms_first_member(bms);
+ RangeTblEntry *rte = root->simple_rte_array[relid];
+
+ if (!first)
+ appendStringInfo(&sql, ", ");
+ get_qualified_relname(rte->relid, &sql);
+ appendStringInfo(&sql, " %s", rte->eref->aliasname);
+ first = false;
+ }
+
+ /* deparse WHERE clause */
+ appendStringInfo(&sql, "\nWHERE ");
+ first = true;
+ foreach(lc, restrict_clauses)
+ {
+ RestrictInfo *ri = (RestrictInfo *) lfirst(lc);
+
+ if (!first)
+ appendStringInfo(&sql, " AND ");
+ appendStringInfo(&sql, "%s",
+ deparse_expression((Node *) ri->clause, context, true, false));
+ first = false;
+ }
+ if (outer_path != NULL)
+ append_where_clause(&sql, context, outer_path, &first);
+ if (inner_path != NULL)
+ append_where_clause(&sql, context, inner_path, &first);
+
+ /* return constructed SQL statement. */
+ elog(DEBUG1, "deparsed join SQL: [%s]", sql.data);
+ return sql.data;
+ }
diff --git a/contrib/pgsql_fdw/pgsql_fdw.c b/contrib/pgsql_fdw/pgsql_fdw.c
index 61d20ee..564f45d 100644
*** a/contrib/pgsql_fdw/pgsql_fdw.c
--- b/contrib/pgsql_fdw/pgsql_fdw.c
*************** static void pgsqlBeginForeignScan(Foreig
*** 109,114 ****
--- 109,123 ----
static TupleTableSlot *pgsqlIterateForeignScan(ForeignScanState *node);
static void pgsqlReScanForeignScan(ForeignScanState *node);
static void pgsqlEndForeignScan(ForeignScanState *node);
+ static FdwPlan *pgsqlPlanForeignJoin(Oid serverid,
+ PlannerInfo *root,
+ RelOptInfo *joinrel,
+ JoinType jointype,
+ SpecialJoinInfo *sjinfo,
+ Path *outer_path,
+ Path *inner_path,
+ List *restrict_clauses,
+ List *pathkeys);
/*
* Helper functions
*************** pgsql_fdw_handler(PG_FUNCTION_ARGS)
*** 141,188 ****
fdwroutine->ReScanForeignScan = pgsqlReScanForeignScan;
fdwroutine->EndForeignScan = pgsqlEndForeignScan;
PG_RETURN_POINTER(fdwroutine);
}
/*
! * pgsqlPlanForeignScan
! * Create a FdwPlan for a scan on the foreign table
*/
! static FdwPlan *
! pgsqlPlanForeignScan(Oid foreigntableid,
! PlannerInfo *root,
! RelOptInfo *baserel)
{
char name[128]; /* must be larger than format + 10 */
StringInfoData cursor;
const char *fetch_count_str;
int fetch_count = DEFAULT_FETCH_COUNT;
- char *sql;
- FdwPlan *fdwplan;
List *fdw_private = NIL;
- ForeignTable *table;
- ForeignServer *server;
-
- /* Construct FdwPlan with cost estimates */
- fdwplan = makeNode(FdwPlan);
- sql = deparseSql(foreigntableid, root, baserel);
- table = GetForeignTable(foreigntableid);
- server = GetForeignServer(table->serverid);
- estimate_costs(root, baserel, sql, server->serverid,
- &fdwplan->startup_cost, &fdwplan->total_cost);
/*
* Store plain SELECT statement in private area of FdwPlan. This will be
* used for executing remote query and explaining scan.
*/
! fdw_private = list_make1(makeString(sql));
/* Use specified fetch_count instead of default value, if any. */
! fetch_count_str = GetFdwOptionValue(InvalidOid, InvalidOid, foreigntableid,
InvalidAttrNumber, "fetch_count");
if (fetch_count_str != NULL)
fetch_count = strtol(fetch_count_str, NULL, 10);
! elog(DEBUG1, "relid=%u fetch_count=%d", foreigntableid, fetch_count);
/*
* We store some more information in FdwPlan to pass them beyond the
--- 150,187 ----
fdwroutine->ReScanForeignScan = pgsqlReScanForeignScan;
fdwroutine->EndForeignScan = pgsqlEndForeignScan;
+ /* Optional handlers. */
+ fdwroutine->PlanForeignJoin = pgsqlPlanForeignJoin;
+
PG_RETURN_POINTER(fdwroutine);
}
/*
! * Make list of private information which are specific to pgsql_fdw.
! * Actual contents are:
! *
*/
! static List *
! make_fdw_private(const char *sql, Oid serverid, Oid relid)
{
char name[128]; /* must be larger than format + 10 */
StringInfoData cursor;
const char *fetch_count_str;
int fetch_count = DEFAULT_FETCH_COUNT;
List *fdw_private = NIL;
/*
* Store plain SELECT statement in private area of FdwPlan. This will be
* used for executing remote query and explaining scan.
*/
! fdw_private = list_make1(makeString(pstrdup(sql)));
/* Use specified fetch_count instead of default value, if any. */
! fetch_count_str = GetFdwOptionValue(InvalidOid, serverid, relid,
InvalidAttrNumber, "fetch_count");
if (fetch_count_str != NULL)
fetch_count = strtol(fetch_count_str, NULL, 10);
! elog(DEBUG1, "relid=%u fetch_count=%d", relid, fetch_count);
/*
* We store some more information in FdwPlan to pass them beyond the
*************** pgsqlPlanForeignScan(Oid foreigntableid,
*** 224,231 ****
appendStringInfo(&cursor, "CLOSE %s", name);
fdw_private = lappend(fdw_private, makeString(cursor.data));
/* Store FDW private information into FdwPlan */
! fdwplan->fdw_private = fdw_private;
return fdwplan;
}
--- 223,256 ----
appendStringInfo(&cursor, "CLOSE %s", name);
fdw_private = lappend(fdw_private, makeString(cursor.data));
+ return fdw_private;
+ }
+
+ /*
+ * pgsqlPlanForeignScan
+ * Create a FdwPlan for a scan on the foreign table
+ */
+ static FdwPlan *
+ pgsqlPlanForeignScan(Oid foreigntableid,
+ PlannerInfo *root,
+ RelOptInfo *baserel)
+ {
+ char *sql;
+ FdwPlan *fdwplan;
+ ForeignTable *table;
+ ForeignServer *server;
+
+ /* Construct FdwPlan with cost estimates */
+ fdwplan = makeNode(FdwPlan);
+ sql = deparseSql(foreigntableid, root, baserel);
+ table = GetForeignTable(foreigntableid);
+ server = GetForeignServer(table->serverid);
+ estimate_costs(root, baserel, sql, server->serverid,
+ &fdwplan->startup_cost, &fdwplan->total_cost);
+
/* Store FDW private information into FdwPlan */
! fdwplan->fdw_private = make_fdw_private(sql, table->serverid,
! foreigntableid);
return fdwplan;
}
*************** pgsqlBeginForeignScan(ForeignScanState *
*** 254,261 ****
{
PgsqlFdwExecutionState *festate;
PGconn *conn;
- Oid relid;
- ForeignTable *table;
ForeignServer *server;
UserMapping *user;
TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
--- 279,284 ----
*************** pgsqlBeginForeignScan(ForeignScanState *
*** 276,284 ****
* Get connection to the foreign server. Connection manager would
* establish new connection if necessary.
*/
! relid = RelationGetRelid(node->ss.ss_currentRelation);
! table = GetForeignTable(relid);
! server = GetForeignServer(table->serverid);
user = GetUserMapping(GetOuterUserId(), server->serverid);
conn = GetConnection(server, user);
festate->conn = conn;
--- 299,305 ----
* Get connection to the foreign server. Connection manager would
* establish new connection if necessary.
*/
! server = GetForeignServer(node->serverid);
user = GetUserMapping(GetOuterUserId(), server->serverid);
conn = GetConnection(server, user);
festate->conn = conn;
*************** pgsqlEndForeignScan(ForeignScanState *no
*** 501,506 ****
--- 522,577 ----
}
/*
+ * pgsqlPlanForeignJoin
+ * Create a FdwPlan for a query which contains of join of foreign tables
+ */
+ static FdwPlan *
+ pgsqlPlanForeignJoin(Oid serverid,
+ PlannerInfo *root,
+ RelOptInfo *joinrel,
+ JoinType jointype,
+ SpecialJoinInfo *sjinfo,
+ Path *outer_path,
+ Path *inner_path,
+ List *restrict_clauses,
+ List *pathkeys)
+ {
+ FdwPlan *fdwplan = NULL;
+ char *sql;
+
+ Assert(joinrel->serverid != InvalidOid);
+ Assert(IsA(outer_path, ForeignPath) || IsA(outer_path, ForeignJoinPath));
+ Assert(IsA(inner_path, ForeignPath) || IsA(inner_path, ForeignJoinPath));
+
+ /*
+ * In this first version, consider only inner join, because OUTER JOIN
+ * might populate more result rows than summary of result of children.
+ */
+ if (jointype != JOIN_INNER)
+ return NULL;
+
+ /*
+ * Deparse join represention into SQL statement which retrieves whole
+ * result of the join. NULL means that pgsql_fdw can't push this join down
+ * to remote side.
+ */
+ sql = deparseJoinSql(serverid, root, joinrel, jointype, sjinfo,
+ outer_path, inner_path, restrict_clauses, pathkeys);
+ if (sql == NULL)
+ return NULL;
+
+ fdwplan = makeNode(FdwPlan);
+ fdwplan->startup_cost = 0.0;
+ fdwplan->total_cost = 0.0;
+ /* TODO estimate cost by executing EXPLAIN on remote side. */
+ estimate_costs(root, joinrel, sql, serverid, &fdwplan->startup_cost,
+ &fdwplan->total_cost);
+ fdwplan->fdw_private = make_fdw_private(sql, serverid, InvalidOid);
+
+ return fdwplan;
+ }
+
+ /*
* Estimate costs of scanning a foreign table.
*/
static void
*************** estimate_costs(PlannerInfo *root, RelOpt
*** 526,535 ****
--- 597,609 ----
* tend to choose custom plan.
*
* See comments in plancache.c for details of custom plan.
+ *
+ * TODO check this in deparse*Sql(), and receive a flag.
*/
foreach(lc, baserel->baserestrictinfo)
{
RestrictInfo *rs = (RestrictInfo *) lfirst(lc);
+
if (contain_ext_param((Node *) rs->clause))
{
*startup_cost = CONNECTION_COSTS;
diff --git a/contrib/pgsql_fdw/pgsql_fdw.h b/contrib/pgsql_fdw/pgsql_fdw.h
index fb49ffb..e0c3ee2 100644
*** a/contrib/pgsql_fdw/pgsql_fdw.h
--- b/contrib/pgsql_fdw/pgsql_fdw.h
*************** int ExtractConnectionOptions(List *defel
*** 24,28 ****
--- 24,37 ----
/* in deparse.c */
char *deparseSql(Oid relid, PlannerInfo *root, RelOptInfo *baserel);
+ char *deparseJoinSql(Oid serverid,
+ PlannerInfo *root,
+ RelOptInfo *joinrel,
+ JoinType jointype,
+ SpecialJoinInfo *sjinfo,
+ Path *outer_path,
+ Path *inner_path,
+ List *restrict_clauses,
+ List *pathkeys);
#endif /* PGSQL_FDW_H */
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index d1e628f..78c5f39 100644
*** a/doc/src/sgml/config.sgml
--- b/doc/src/sgml/config.sgml
*************** SET ENABLE_SEQSCAN TO OFF;
*** 2362,2367 ****
--- 2362,2380 ----
+
+ enable_foreignjoin (boolean)
+
+ enable_foreignjoin> configuration parameter
+
+
+
+ Enables or disables the query planner's use of foreign-join plan
+ types. The default is on>.
+
+
+
+
enable_hashagg (boolean)
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index d809cac..7e3c5bd 100644
*** a/doc/src/sgml/fdwhandler.sgml
--- b/doc/src/sgml/fdwhandler.sgml
*************** EndForeignScan (ForeignScanState *node);
*** 228,233 ****
--- 228,273 ----
+ Callback functions above are required for every foreign data wrappers.
+ In contrast, callback functions below are optional, so you can set each of
+ them to NULL to tell the planner and/or executor that the FDW doesn't
+ support the feature. Actually, in such case, you don't have to set NULL
+ explicitly because makeNode has already initialized
+ them to NULL.
+
+
+
+
+ FdwPlan *
+ PlanForeignJoin (Oid serverid,
+ PlannerInfo *root,
+ RelOptInfo *joinrel,
+ JoinType jointype,
+ SpecialJoinInfo *sjinfo,
+ Path *outer_path,
+ Path *inner_path,
+ List *restrict_clauses,
+ List *pathkeys);
+
+
+ Plan a join between two foreign scans performed on same server. In this
+ context, a foreign scans can be a simple foreign table reference planned
+ by PlanForeignScan, or a result of a foreign join
+ planned by PlanForeignJoin.
+ Arguments outer_path and inner_path
+ provide details of each child nodes, and other arguments provide details
+ of the join itself.
+
+
+
+ This function is called during planning to determine whether foreign join
+ can be pushed down. FDW can use private area of
+ FdwPlan, similarly to
+ PlanForeignScan, to store FDW-specific information
+ which are needed to get result of the join.
+
+
+
The FdwRoutine> and FdwPlan> struct types
are declared in src/include/foreign/fdwapi.h>, which see
for additional details.
*************** GetForeignTable(Oid relid);
*** 298,303 ****
--- 338,362 ----
+ char *
+ GetFdwOptionValue(Oid relid, AttrNumber attnum, const char *optname);
+
+
+ This function returns a copy (created in current memory context) of the
+ value of the given option for the given object (relation or its column).
+ If attnum is InvalidAttrNumber, pg_attribute is ignored.
+ If specified option is set in multiple object level, the one in the
+ finest-grained object is used; e.g. priority is given to user mapping
+ over than a foreign server for the mapping or foreign-data wrapper for the
+ server.
+ This function would be useful when you know which option is needed but you
+ don't know which object(s) have it.
+ If you already know the source object, it would be more efficient to use
+ object retrieval functions.
+
+
+
+
List *
GetForeignTableColumnOptions(Oid relid, AttrNumber attnum);
*************** GetForeignServerByName(const char *name,
*** 334,388 ****
-
- char *
- GetFdwOptionValue(Oid fdwid, Oid serverid, Oid relid, AttrNumber attnum, const char *optname);
-
-
- This function returns a copied string (created in current memory context)
- of the value of a FDW option with given name which is set on a object with
- given oid and attribute number. This function ignores catalogs if invalid
- identifir is given for it.
-
-
-
-
- If attnum is InvalidAttrNumber or relid is
- Invalidoid, pg_attribute is
- ignored.
-
-
-
-
- If relid is InvalidOid,
- pg_foreign_table is ignored.
-
-
-
-
- If both serverid and relid are InvalidOid,
- pg_foreign_server is ignored.
-
-
-
-
- If all of fdwid, serverid and relid are InvalidOid,
- pg_foreign_data_wrapper is ignored.
-
-
-
-
-
-
- If the option with given name is set in multiple object level, the one in
- the finest-grained object is used; e.g. priority is given to user mappings
- over than foreign servers.
- This function would be useful when you know which option is needed but you
- don't know where it is set. If you already know the source object, it
- would be more efficient to use object retrieval functions.
-
-
-
To use any of these functions, you need to include
foreign/foreign.h in your source file.
--- 393,398 ----
diff --git a/doc/src/sgml/ref/postgres-ref.sgml b/doc/src/sgml/ref/postgres-ref.sgml
index 9869a1f..d785c2f 100644
*** a/doc/src/sgml/ref/postgres-ref.sgml
--- b/doc/src/sgml/ref/postgres-ref.sgml
*************** PostgreSQL documentation
*** 376,382 ****
! { s | i | o | b | t | n | m | h }
Forbids the use of particular scan and join methods:
--- 376,382 ----
! { s | i | o | b | t | n | m | h | f }
Forbids the use of particular scan and join methods:
*************** PostgreSQL documentation
*** 385,392 ****
o, b and t
disable index-only scans, bitmap index scans, and TID scans
respectively, while
! n, m, and h
! disable nested-loop, merge and hash joins respectively.
--- 385,393 ----
o, b and t
disable index-only scans, bitmap index scans, and TID scans
respectively, while
! n, m, h, and
! f disable nested-loop, merge joins, hash joins, and
! foreign joins respectively.
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index e38de5c..86e0203 100644
*** a/src/backend/commands/explain.c
--- b/src/backend/commands/explain.c
*************** static const char *explain_get_index_nam
*** 82,87 ****
--- 82,88 ----
static void ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir,
ExplainState *es);
static void ExplainScanTarget(Scan *plan, ExplainState *es);
+ static void ExplainForeignScanTarget(Scan *plan, ExplainState *es);
static void ExplainModifyTarget(ModifyTable *plan, ExplainState *es);
static void ExplainTargetRel(Plan *plan, Index rti, ExplainState *es);
static void ExplainMemberNodes(List *plans, PlanState **planstates,
*************** ExplainNode(PlanState *planstate, List *
*** 803,811 ****
case T_ValuesScan:
case T_CteScan:
case T_WorkTableScan:
- case T_ForeignScan:
ExplainScanTarget((Scan *) plan, es);
break;
case T_IndexScan:
{
IndexScan *indexscan = (IndexScan *) plan;
--- 804,814 ----
case T_ValuesScan:
case T_CteScan:
case T_WorkTableScan:
ExplainScanTarget((Scan *) plan, es);
break;
+ case T_ForeignScan:
+ ExplainForeignScanTarget((Scan *) plan, es);
+ break;
case T_IndexScan:
{
IndexScan *indexscan = (IndexScan *) plan;
*************** ExplainScanTarget(Scan *plan, ExplainSta
*** 1674,1679 ****
--- 1677,1702 ----
}
/*
+ * Show the target of a ForeignScan node
+ */
+ static void
+ ExplainForeignScanTarget(Scan *plan, ExplainState *es)
+ {
+ Assert(IsA(plan, ForeignScan));
+
+ /*
+ * If scan target is an foreign table, show in normal scan format,
+ * otherwise, show in specific format.
+ */
+ if (plan->scanrelid > 0)
+ ExplainTargetRel((Plan *) plan, plan->scanrelid, es);
+ else
+ {
+ appendStringInfo(es->str, " on multiple foreign tables");
+ }
+ }
+
+ /*
* Show the target of a ModifyTable node
*/
static void
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 65591e2..e7adb74 100644
*** a/src/backend/executor/execUtils.c
--- b/src/backend/executor/execUtils.c
*************** ExecAssignScanTypeFromOuterPlan(ScanStat
*** 760,765 ****
--- 760,790 ----
ExecAssignScanType(scanstate, tupDesc);
}
+ /* ----------------
+ * ExecAssignScanTypeFromTL
+ * ----------------
+ */
+ void
+ ExecAssignScanTypeFromTL(ScanState *scanstate)
+ {
+ bool hasoid;
+ TupleTableSlot *slot = scanstate->ss_ScanTupleSlot;
+ TupleDesc tupDesc;
+
+ if (ExecContextForcesOids(&scanstate->ps, &hasoid))
+ {
+ /* context forces OID choice; hasoid is now set correctly */
+ }
+ else
+ {
+ /* given free choice, don't leave space for OIDs in result tuples */
+ hasoid = false;
+ }
+
+ tupDesc = ExecTypeFromTL(scanstate->ps.plan->targetlist, hasoid);
+ ExecSetSlotDescriptor(slot, tupDesc);
+ }
+
/* ----------------------------------------------------------------
* Scan node support
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 841ae69..4bf6d6c 100644
*** a/src/backend/executor/nodeForeignscan.c
--- b/src/backend/executor/nodeForeignscan.c
***************
*** 25,30 ****
--- 25,31 ----
#include "executor/executor.h"
#include "executor/nodeForeignscan.h"
#include "foreign/fdwapi.h"
+ #include "foreign/foreign.h"
#include "utils/rel.h"
static TupleTableSlot *ForeignNext(ForeignScanState *node);
*************** ExecForeignScan(ForeignScanState *node)
*** 101,109 ****
ForeignScanState *
ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
{
! ForeignScanState *scanstate;
! Relation currentRelation;
! FdwRoutine *fdwroutine;
/* check for unsupported flags */
Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
--- 102,112 ----
ForeignScanState *
ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
{
! ForeignScanState *scanstate;
! Relation currentRelation;
! ForeignServer *server;
! ForeignDataWrapper *wrapper;
! FdwRoutine *fdwroutine;
/* check for unsupported flags */
Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
*************** ExecInitForeignScan(ForeignScan *node, E
*** 140,166 ****
ExecInitResultTupleSlot(estate, &scanstate->ss.ps);
ExecInitScanTupleSlot(estate, &scanstate->ss);
! /*
! * open the base relation and acquire appropriate lock on it.
! */
! currentRelation = ExecOpenScanRelation(estate, node->scan.scanrelid);
! scanstate->ss.ss_currentRelation = currentRelation;
! /*
! * get the scan type from the relation descriptor.
! */
! ExecAssignScanType(&scanstate->ss, RelationGetDescr(currentRelation));
! /*
! * Initialize result tuple type and projection info.
! */
! ExecAssignResultTypeFromTL(&scanstate->ss.ps);
! ExecAssignScanProjectionInfo(&scanstate->ss);
/*
* Acquire function pointers from the FDW's handler, and init fdw_state.
*/
! fdwroutine = GetFdwRoutineByRelId(RelationGetRelid(currentRelation));
scanstate->fdwroutine = fdwroutine;
scanstate->fdw_state = NULL;
--- 143,193 ----
ExecInitResultTupleSlot(estate, &scanstate->ss.ps);
ExecInitScanTupleSlot(estate, &scanstate->ss);
! if (node->scan.scanrelid != InvalidOid)
! {
! /*
! * open the base relation and acquire appropriate lock on it.
! */
! currentRelation = ExecOpenScanRelation(estate, node->scan.scanrelid);
! scanstate->ss.ss_currentRelation = currentRelation;
! /*
! * get the scan type from the relation descriptor.
! */
! ExecAssignScanType(&scanstate->ss, RelationGetDescr(currentRelation));
! /*
! * Initialize result tuple type and projection info.
! */
! ExecAssignResultTypeFromTL(&scanstate->ss.ps);
! ExecAssignScanProjectionInfo(&scanstate->ss);
! }
! else
! {
! /* TODO: open related relations and acquire appropriate lock on them. */
! scanstate->ss.ss_currentRelation = NULL;
!
! /*
! * get the scan type from the target list.
! */
! ExecAssignScanTypeFromTL(&scanstate->ss);
!
! /*
! * Initialize result tuple type and projection info.
! */
! ExecAssignResultTypeFromTL(&scanstate->ss.ps);
! ExecAssignProjectionInfo(&scanstate->ss.ps, NULL);
! }
!
! /* We know which server should be use. */
! scanstate->serverid = node->serverid;
/*
* Acquire function pointers from the FDW's handler, and init fdw_state.
*/
! server = GetForeignServer(node->serverid);
! wrapper = GetForeignDataWrapper(server->fdwid);
! fdwroutine = GetFdwRoutine(wrapper->fdwhandler);
scanstate->fdwroutine = fdwroutine;
scanstate->fdw_state = NULL;
*************** ExecEndForeignScan(ForeignScanState *nod
*** 192,198 ****
ExecClearTuple(node->ss.ss_ScanTupleSlot);
/* close the relation. */
! ExecCloseScanRelation(node->ss.ss_currentRelation);
}
/* ----------------------------------------------------------------
--- 219,226 ----
ExecClearTuple(node->ss.ss_ScanTupleSlot);
/* close the relation. */
! if (node->ss.ss_currentRelation != NULL)
! ExecCloseScanRelation(node->ss.ss_currentRelation);
}
/* ----------------------------------------------------------------
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
index b984a5e..7c77847 100644
*** a/src/backend/foreign/foreign.c
--- b/src/backend/foreign/foreign.c
*************** GetFdwOptionValue(Oid fdwid, Oid serveri
*** 292,299 ****
ForeignDataWrapper *wrapper = NULL;
char *value;
! /* Do we need to search pg_attribute? */
! if (attnum != InvalidAttrNumber && relid != InvalidOid)
{
value = get_options_value(GetForeignColumnOptions(relid, attnum),
optname);
--- 292,299 ----
ForeignDataWrapper *wrapper = NULL;
char *value;
! /* Do we need to use pg_attribute.attfdwoptions too? */
! if (attnum != InvalidAttrNumber)
{
value = get_options_value(GetForeignColumnOptions(relid, attnum),
optname);
*************** GetFdwOptionValue(Oid fdwid, Oid serveri
*** 301,334 ****
return value;
}
- /* Do we need to search pg_foreign_table? */
if (relid != InvalidOid)
{
table = GetForeignTable(relid);
value = get_options_value(table->options, optname);
if (value != NULL)
return value;
-
serverid = table->serverid;
}
- /* Do we need to search pg_user_mapping and pg_foreign_server? */
if (serverid != InvalidOid)
{
user = GetUserMapping(GetOuterUserId(), serverid);
value = get_options_value(user->options, optname);
if (value != NULL)
return value;
server = GetForeignServer(serverid);
value = get_options_value(server->options, optname);
if (value != NULL)
return value;
-
fdwid = server->fdwid;
}
- /* Do we need to search pg_foreign_data_wrapper? */
if (fdwid != InvalidOid)
{
wrapper = GetForeignDataWrapper(fdwid);
--- 301,332 ----
return value;
}
if (relid != InvalidOid)
{
table = GetForeignTable(relid);
value = get_options_value(table->options, optname);
if (value != NULL)
return value;
serverid = table->serverid;
}
if (serverid != InvalidOid)
{
user = GetUserMapping(GetOuterUserId(), serverid);
value = get_options_value(user->options, optname);
if (value != NULL)
return value;
+ }
+ if (serverid != InvalidOid)
+ {
server = GetForeignServer(serverid);
value = get_options_value(server->options, optname);
if (value != NULL)
return value;
fdwid = server->fdwid;
}
if (fdwid != InvalidOid)
{
wrapper = GetForeignDataWrapper(fdwid);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 63958c3..df3f25a 100644
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
*************** _copyForeignScan(ForeignScan *from)
*** 590,595 ****
--- 590,596 ----
/*
* copy remainder of node
*/
+ COPY_SCALAR_FIELD(serverid);
COPY_SCALAR_FIELD(fsSystemCol);
COPY_NODE_FIELD(fdwplan);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index f7d39ed..85f4071 100644
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
*************** _outForeignScan(StringInfo str, ForeignS
*** 557,562 ****
--- 557,563 ----
_outScanInfo(str, (Scan *) node);
+ WRITE_OID_FIELD(serverid);
WRITE_BOOL_FIELD(fsSystemCol);
WRITE_NODE_FIELD(fdwplan);
}
*************** _outHashPath(StringInfo str, HashPath *n
*** 1661,1666 ****
--- 1662,1677 ----
}
static void
+ _outForeignJoinPath(StringInfo str, ForeignJoinPath *node)
+ {
+ WRITE_NODE_TYPE("FOREIGNJOINPATH");
+
+ _outJoinPathInfo(str, (JoinPath *) node);
+
+ WRITE_NODE_FIELD(fdwplan);
+ }
+
+ static void
_outPlannerGlobal(StringInfo str, PlannerGlobal *node)
{
WRITE_NODE_TYPE("PLANNERGLOBAL");
*************** _outRelOptInfo(StringInfo str, RelOptInf
*** 1749,1754 ****
--- 1760,1766 ----
WRITE_NODE_FIELD(baserestrictinfo);
WRITE_NODE_FIELD(joininfo);
WRITE_BOOL_FIELD(has_eclass_joins);
+ WRITE_OID_FIELD(serverid);
WRITE_BITMAPSET_FIELD(index_outer_relids);
WRITE_NODE_FIELD(index_inner_paths);
}
*************** _outNode(StringInfo str, void *obj)
*** 2987,2992 ****
--- 2999,3007 ----
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/README b/src/backend/optimizer/README
index aaa754c..f06cb27 100644
*** a/src/backend/optimizer/README
--- b/src/backend/optimizer/README
*************** RelOptInfo - a relation or joined r
*** 356,361 ****
--- 356,362 ----
NestPath - nested-loop joins
MergePath - merge joins
HashPath - hash joins
+ ForeignJoinPath - foreign joins
EquivalenceClass - a data structure representing a set of values known equal
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 815b996..17b8cbe 100644
*** a/src/backend/optimizer/path/allpaths.c
--- b/src/backend/optimizer/path/allpaths.c
*************** print_path(PlannerInfo *root, Path *path
*** 1610,1615 ****
--- 1610,1619 ----
ptype = "HashJoin";
join = true;
break;
+ case T_ForeignJoinPath:
+ ptype = "ForeignJoin";
+ join = true;
+ break;
default:
ptype = "???Path";
break;
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 348c36b..def47ee 100644
*** a/src/backend/optimizer/path/costsize.c
--- b/src/backend/optimizer/path/costsize.c
*************** bool enable_nestloop = true;
*** 119,124 ****
--- 119,125 ----
bool enable_material = true;
bool enable_mergejoin = true;
bool enable_hashjoin = true;
+ bool enable_foreignjoin = true;
typedef struct
{
*************** cost_mergejoin(MergePath *path, PlannerI
*** 2100,2105 ****
--- 2101,2111 ----
}
/*
+ * cost_foreignjoin() is not defined here because the costs of a foreign join
+ * is estimated by each FDW via PlanForeignJoin.
+ */
+
+ /*
* run mergejoinscansel() with caching
*/
static MergeScanSelCache *
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 7d3cf42..3024565 100644
*** a/src/backend/optimizer/path/joinpath.c
--- b/src/backend/optimizer/path/joinpath.c
*************** match_unsorted_outer(PlannerInfo *root,
*** 541,546 ****
--- 541,566 ----
merge_pathkeys));
}
+ if (enable_foreignjoin &&
+ joinrel->serverid != InvalidOid &&
+ (IsA(outerpath, ForeignPath) || IsA(outerpath, ForeignJoinPath)) &&
+ (IsA(inner_cheapest_total, ForeignPath) ||
+ IsA(inner_cheapest_total, ForeignJoinPath)))
+
+ {
+ ForeignJoinPath *path;
+ path = create_foreignjoin_path(root,
+ joinrel,
+ jointype,
+ sjinfo,
+ outerpath,
+ inner_cheapest_total,
+ restrictlist,
+ merge_pathkeys);
+ if (path != NULL)
+ add_path(joinrel, (Path *) path);
+ }
+
/* Can't do anything else if outer path needs to be unique'd */
if (save_jointype == JOIN_UNIQUE_OUTER)
continue;
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 8138b01..8a1f1c7 100644
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
*************** static WorkTableScan *create_worktablesc
*** 75,80 ****
--- 75,82 ----
List *tlist, List *scan_clauses);
static ForeignScan *create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
List *tlist, List *scan_clauses);
+ static ForeignScan *create_foreignjoin_plan(PlannerInfo *root, ForeignJoinPath *best_path,
+ List *tlist, List *scan_clauses);
static NestLoop *create_nestloop_plan(PlannerInfo *root, NestPath *best_path,
Plan *outer_plan, Plan *inner_plan);
static MergeJoin *create_mergejoin_plan(PlannerInfo *root, MergePath *best_path,
*************** static CteScan *make_ctescan(List *qptli
*** 123,129 ****
static WorkTableScan *make_worktablescan(List *qptlist, List *qpqual,
Index scanrelid, int wtParam);
static ForeignScan *make_foreignscan(List *qptlist, List *qpqual,
! Index scanrelid, bool fsSystemCol, FdwPlan *fdwplan);
static BitmapAnd *make_bitmap_and(List *bitmapplans);
static BitmapOr *make_bitmap_or(List *bitmapplans);
static NestLoop *make_nestloop(List *tlist,
--- 125,132 ----
static WorkTableScan *make_worktablescan(List *qptlist, List *qpqual,
Index scanrelid, int wtParam);
static ForeignScan *make_foreignscan(List *qptlist, List *qpqual,
! Index scanrelid, Oid serverid, bool fsSystemCol,
! FdwPlan *fdwplan);
static BitmapAnd *make_bitmap_and(List *bitmapplans);
static BitmapOr *make_bitmap_or(List *bitmapplans);
static NestLoop *make_nestloop(List *tlist,
*************** create_plan_recurse(PlannerInfo *root, P
*** 221,226 ****
--- 224,230 ----
case T_CteScan:
case T_WorkTableScan:
case T_ForeignScan:
+ case T_ForeignJoin: /* ForeignJoinPath become a ForeignScan */
plan = create_scan_plan(root, best_path);
break;
case T_HashJoin:
*************** create_scan_plan(PlannerInfo *root, Path
*** 385,390 ****
--- 389,401 ----
scan_clauses);
break;
+ case T_ForeignJoin:
+ plan = (Plan *) create_foreignjoin_plan(root,
+ (ForeignJoinPath *) best_path,
+ tlist,
+ scan_clauses);
+ break;
+
default:
elog(ERROR, "unrecognized node type: %d",
(int) best_path->pathtype);
*************** create_foreignscan_plan(PlannerInfo *roo
*** 1856,1861 ****
--- 1867,1873 ----
scan_plan = make_foreignscan(tlist,
scan_clauses,
scan_relid,
+ rel->serverid,
fsSystemCol,
best_path->fdwplan);
*************** create_foreignscan_plan(PlannerInfo *roo
*** 1864,1869 ****
--- 1876,1926 ----
return scan_plan;
}
+ /*
+ * create_foreignjoin_plan
+ * Returns a foreignscan plan for the join relation joined by 'best_path'
+ * with restriction clauses 'scan_clauses' and targetlist 'tlist'.
+ */
+ static ForeignScan *
+ create_foreignjoin_plan(PlannerInfo *root, ForeignJoinPath *best_path,
+ List *tlist, List *scan_clauses)
+ {
+ ForeignScan *scan_plan;
+ RelOptInfo *rel = best_path->jpath.path.parent;
+ Index scan_relid = rel->relid;
+ bool fsSystemCol;
+
+ /* Sort clauses into best execution order */
+ scan_clauses = order_qual_clauses(root, scan_clauses);
+
+ /* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
+ scan_clauses = extract_actual_clauses(scan_clauses, false);
+
+ fsSystemCol = false;
+ #ifdef NOT_USED
+ /* Detect whether any system columns are requested from rel */
+ for (i = rel->min_attr; i < 0; i++)
+ {
+ if (!bms_is_empty(rel->attr_needed[i - rel->min_attr]))
+ {
+ fsSystemCol = true;
+ break;
+ }
+ }
+ #endif
+
+ scan_plan = make_foreignscan(tlist,
+ scan_clauses,
+ scan_relid,
+ rel->serverid,
+ fsSystemCol,
+ best_path->fdwplan);
+
+ copy_path_costsize(&scan_plan->scan.plan, &best_path->jpath.path);
+
+ return scan_plan;
+ }
+
/*****************************************************************************
*
*************** static ForeignScan *
*** 3170,3175 ****
--- 3227,3233 ----
make_foreignscan(List *qptlist,
List *qpqual,
Index scanrelid,
+ Oid serverid,
bool fsSystemCol,
FdwPlan *fdwplan)
{
*************** make_foreignscan(List *qptlist,
*** 3182,3187 ****
--- 3240,3246 ----
plan->lefttree = NULL;
plan->righttree = NULL;
node->scan.scanrelid = scanrelid;
+ node->serverid = serverid;
node->fsSystemCol = fsSystemCol;
node->fdwplan = fdwplan;
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 1e7aac9..ea5095b 100644
*** a/src/backend/optimizer/util/pathnode.c
--- b/src/backend/optimizer/util/pathnode.c
***************
*** 17,22 ****
--- 17,23 ----
#include
#include "foreign/fdwapi.h"
+ #include "foreign/foreign.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
*************** create_hashjoin_path(PlannerInfo *root,
*** 1629,1631 ****
--- 1630,1712 ----
return pathnode;
}
+
+ /*
+ * create_foreignjoin_path
+ * Creates a pathnode corresponding to a foreign join between two
+ * relations.
+ *
+ * 'joinrel' is the join relation.
+ * 'jointype' is the type of join required
+ * 'sjinfo' is extra info about the join for selectivity estimation
+ * 'outer_path' is the outer path
+ * 'inner_path' is the inner path
+ * 'restrict_clauses' are the RestrictInfo nodes to apply at the join
+ * 'pathkeys' are the path keys of the new join path
+ *
+ * Returns the resulting path node, or NULL to indicate that this path is
+ * unavailable.
+ */
+ ForeignJoinPath *
+ create_foreignjoin_path(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ JoinType jointype,
+ SpecialJoinInfo *sjinfo,
+ Path *outer_path,
+ Path *inner_path,
+ List *restrict_clauses,
+ List *pathkeys)
+ {
+ ForeignJoinPath *pathnode;
+ ForeignServer *server;
+ ForeignDataWrapper *wrapper;
+ FdwRoutine *fdwroutine;
+ FdwPlan *fdwplan;
+
+ /* Both outer and inner of this join must come from same foreign server. */
+ Assert(IsA(outer_path, ForeignPath) || IsA(outer_path, ForeignJoinPath));
+ Assert(IsA(inner_path, ForeignPath) || IsA(inner_path, ForeignJoinPath));
+
+ /*
+ * First we try to get FDW's callback info. If the FDW has planner for
+ * foreign join, let the FDW plan this join.
+ */
+ server = GetForeignServer(joinrel->serverid);
+ wrapper = GetForeignDataWrapper(server->fdwid);
+ fdwroutine = GetFdwRoutine(wrapper->fdwhandler);
+ if (fdwroutine->PlanForeignJoin == NULL)
+ return NULL;
+
+ fdwplan = fdwroutine->PlanForeignJoin(joinrel->serverid,
+ root,
+ joinrel,
+ jointype,
+ sjinfo,
+ outer_path,
+ inner_path,
+ restrict_clauses,
+ pathkeys);
+ /* Returning NULL indicates that the FDW can't handle this join. */
+ if (fdwplan == NULL)
+ return NULL;
+ Assert(IsA(fdwplan, FdwPlan));
+
+ /* OK, this FDW can handle this join. */
+ pathnode = makeNode(ForeignJoinPath);
+ pathnode->jpath.path.pathtype = T_ForeignJoin;
+ pathnode->jpath.path.parent = joinrel;
+ pathnode->jpath.jointype = jointype;
+ pathnode->jpath.outerjoinpath = outer_path;
+ pathnode->jpath.innerjoinpath = inner_path;
+ pathnode->jpath.joinrestrictinfo = restrict_clauses;
+ pathnode->jpath.path.pathkeys = pathkeys;
+
+ /* Use costs estimated by FDW */
+ pathnode->jpath.path.startup_cost = fdwplan->startup_cost;
+ pathnode->jpath.path.total_cost = fdwplan->total_cost;
+
+ /* Store FDW-private information too. */
+ pathnode->fdwplan = fdwplan;
+
+ return pathnode;
+ }
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index de629e9..d69be69 100644
*** a/src/backend/optimizer/util/plancat.c
--- b/src/backend/optimizer/util/plancat.c
***************
*** 22,27 ****
--- 22,28 ----
#include "access/sysattr.h"
#include "access/transam.h"
#include "catalog/catalog.h"
+ #include "foreign/foreign.h"
#include "catalog/heap.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
*************** get_relation_info(PlannerInfo *root, Oid
*** 359,364 ****
--- 360,374 ----
rel->indexlist = indexinfos;
+ /* Get server oid for further planning, if this is a foreign table. */
+ if (relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ ForeignTable *table;
+
+ table = GetForeignTable(relationObjectId);
+ rel->serverid = table->serverid;
+ }
+
heap_close(relation, NoLock);
/*
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 37187e2..24cfe24 100644
*** a/src/backend/optimizer/util/relnode.c
--- b/src/backend/optimizer/util/relnode.c
*************** build_simple_rel(PlannerInfo *root, int
*** 117,122 ****
--- 117,123 ----
rel->baserestrictcost.per_tuple = 0;
rel->joininfo = NIL;
rel->has_eclass_joins = false;
+ rel->serverid = InvalidOid;
rel->index_outer_relids = NULL;
rel->index_inner_paths = NIL;
*************** build_join_rel(PlannerInfo *root,
*** 371,376 ****
--- 372,378 ----
joinrel->baserestrictcost.per_tuple = 0;
joinrel->joininfo = NIL;
joinrel->has_eclass_joins = false;
+ joinrel->serverid = InvalidOid;
joinrel->index_outer_relids = NULL;
joinrel->index_inner_paths = NIL;
*************** build_join_rel(PlannerInfo *root,
*** 443,448 ****
--- 445,458 ----
lappend(root->join_rel_level[root->join_cur_level], joinrel);
}
+ /*
+ * If both outer and inner are from one oreign server, maybe this join can
+ * be pushed down, so remember the oid of the foreign server in this
+ * relation.
+ */
+ if (outer_rel->serverid == inner_rel->serverid)
+ joinrel->serverid = outer_rel->serverid;
+
return joinrel;
}
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 976a832..e97a94d 100644
*** a/src/backend/tcop/postgres.c
--- b/src/backend/tcop/postgres.c
*************** set_plan_disabling_options(const char *a
*** 3093,3098 ****
--- 3093,3101 ----
case 'h': /* hashjoin */
tmp = "enable_hashjoin";
break;
+ case 'f': /* foreignjoin */
+ tmp = "enable_foreignjoin";
+ break;
}
if (tmp)
{
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index da7b6d4..524582d 100644
*** a/src/backend/utils/misc/guc.c
--- b/src/backend/utils/misc/guc.c
*************** static struct config_bool ConfigureNames
*** 765,770 ****
--- 765,779 ----
NULL, NULL, NULL
},
{
+ {"enable_foreignjoin", PGC_USERSET, QUERY_TUNING_METHOD,
+ gettext_noop("Enables the planner's use of foreign join plans."),
+ NULL
+ },
+ &enable_foreignjoin,
+ true,
+ NULL, NULL, NULL
+ },
+ {
{"geqo", PGC_USERSET, QUERY_TUNING_GEQO,
gettext_noop("Enables genetic query optimization."),
gettext_noop("This algorithm attempts to do planning without "
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 315db46..d1dec0e 100644
*** a/src/backend/utils/misc/postgresql.conf.sample
--- b/src/backend/utils/misc/postgresql.conf.sample
***************
*** 240,245 ****
--- 240,246 ----
# - Planner Method Configuration -
#enable_bitmapscan = on
+ #enable_foreignjoin = on
#enable_hashagg = on
#enable_hashjoin = on
#enable_indexscan = on
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index bdd499b..e4d3426 100644
*** a/src/include/executor/executor.h
--- b/src/include/executor/executor.h
*************** extern void ExecFreeExprContext(PlanStat
*** 332,337 ****
--- 332,338 ----
extern TupleDesc ExecGetScanType(ScanState *scanstate);
extern void ExecAssignScanType(ScanState *scanstate, TupleDesc tupDesc);
extern void ExecAssignScanTypeFromOuterPlan(ScanState *scanstate);
+ extern void ExecAssignScanTypeFromTL(ScanState *scanstate);
extern bool ExecRelationIsTargetRelation(EState *estate, Index scanrelid);
diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
index 3378ba9..e535e12 100644
*** a/src/include/foreign/fdwapi.h
--- b/src/include/foreign/fdwapi.h
*************** typedef void (*ReScanForeignScan_functio
*** 68,73 ****
--- 68,83 ----
typedef void (*EndForeignScan_function) (ForeignScanState *node);
+ typedef FdwPlan *(*PlanForeignJoin_function) (Oid serverid,
+ PlannerInfo *root,
+ RelOptInfo *joinrel,
+ JoinType jointype,
+ SpecialJoinInfo *sjinfo,
+ Path *outer_path,
+ Path *inner_path,
+ List *restrict_clauses,
+ List *pathkeys);
+
/*
* FdwRoutine is the struct returned by a foreign-data wrapper's handler
*************** typedef struct FdwRoutine
*** 88,93 ****
--- 98,106 ----
IterateForeignScan_function IterateForeignScan;
ReScanForeignScan_function ReScanForeignScan;
EndForeignScan_function EndForeignScan;
+
+ /* functions below are optional */
+ PlanForeignJoin_function PlanForeignJoin;
} FdwRoutine;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 0a89f18..27d37a0 100644
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
*************** typedef struct WorkTableScanState
*** 1463,1468 ****
--- 1463,1469 ----
typedef struct ForeignScanState
{
ScanState ss; /* its first field is NodeTag */
+ Oid serverid; /* OID of foreign server */
/* use struct pointer to avoid including fdwapi.h here */
struct FdwRoutine *fdwroutine;
void *fdw_state; /* foreign-data wrapper can keep state here */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 824d8b5..0f5dac2 100644
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
*************** typedef enum NodeTag
*** 67,72 ****
--- 67,73 ----
T_NestLoop,
T_MergeJoin,
T_HashJoin,
+ T_ForeignJoin,
T_Material,
T_Sort,
T_Group,
*************** typedef enum NodeTag
*** 221,226 ****
--- 222,228 ----
T_NestPath,
T_MergePath,
T_HashPath,
+ T_ForeignJoinPath,
T_TidPath,
T_ForeignPath,
T_AppendPath,
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 6685864..2fc8a36 100644
*** a/src/include/nodes/plannodes.h
--- b/src/include/nodes/plannodes.h
*************** typedef struct WorkTableScan
*** 467,472 ****
--- 467,473 ----
typedef struct ForeignScan
{
Scan scan;
+ Oid serverid; /* OID of foreign server */
bool fsSystemCol; /* true if any "system column" is needed */
/* use struct pointer to avoid including fdwapi.h here */
struct FdwPlan *fdwplan;
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index a400960..d790a34 100644
*** a/src/include/nodes/relation.h
--- b/src/include/nodes/relation.h
*************** typedef struct RelOptInfo
*** 416,421 ****
--- 416,422 ----
List *joininfo; /* RestrictInfo structures for join clauses
* involving this rel */
bool has_eclass_joins; /* T means joininfo is incomplete */
+ Oid serverid; /* foriegn server, if foreign scan/join */
/* cached info about inner indexscan paths for relation: */
Relids index_outer_relids; /* other relids in indexable join
*************** typedef struct HashPath
*** 954,959 ****
--- 955,970 ----
} HashPath;
/*
+ * A foreignjoin path has no additional field.
+ */
+ typedef struct ForeignJoinPath
+ {
+ JoinPath jpath;
+ /* use struct pointer to avoid including fdwapi.h here */
+ struct FdwPlan *fdwplan; /* FDW-specific information */
+ } ForeignJoinPath;
+
+ /*
* Restriction clause info.
*
* We create one of these for each AND sub-clause of a restriction condition
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index 125808a..284eb92 100644
*** a/src/include/optimizer/cost.h
--- b/src/include/optimizer/cost.h
*************** extern bool enable_nestloop;
*** 61,66 ****
--- 61,67 ----
extern bool enable_material;
extern bool enable_mergejoin;
extern bool enable_hashjoin;
+ extern bool enable_foreignjoin;
extern int constraint_exclusion;
extern double clamp_row_est(double nrows);
*************** extern void cost_mergejoin(MergePath *pa
*** 114,119 ****
--- 115,124 ----
SpecialJoinInfo *sjinfo);
extern void cost_hashjoin(HashPath *path, PlannerInfo *root,
SpecialJoinInfo *sjinfo);
+ /*
+ * cost_foreignjoin() is not defined here because the costs of a foreign join
+ * is estimated by each FDW via PlanForeignJoin.
+ */
extern void cost_subplan(PlannerInfo *root, SubPlan *subplan, Plan *plan);
extern void cost_qual_eval(QualCost *cost, List *quals, PlannerInfo *root);
extern void cost_qual_eval_node(QualCost *cost, Node *qual, PlannerInfo *root);
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 38c8c1c..b303270 100644
*** a/src/include/optimizer/pathnode.h
--- b/src/include/optimizer/pathnode.h
*************** extern HashPath *create_hashjoin_path(Pl
*** 94,99 ****
--- 94,108 ----
List *restrict_clauses,
List *hashclauses);
+ extern ForeignJoinPath *create_foreignjoin_path(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ JoinType jointype,
+ SpecialJoinInfo *sjinfo,
+ Path *outer_path,
+ Path *inner_path,
+ List *restrict_clauses,
+ List *pathkeys);
+
/*
* prototypes for relnode.c
*/
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index 5f20c93..8a81674 100644
*** a/src/test/regress/expected/rangefuncs.out
--- b/src/test/regress/expected/rangefuncs.out
*************** SELECT name, setting FROM pg_settings WH
*** 2,7 ****
--- 2,8 ----
name | setting
----------------------+---------
enable_bitmapscan | on
+ enable_foreignjoin | on
enable_hashagg | on
enable_hashjoin | on
enable_indexonlyscan | on
*************** SELECT name, setting FROM pg_settings WH
*** 12,18 ****
enable_seqscan | on
enable_sort | on
enable_tidscan | on
! (11 rows)
CREATE TABLE foo2(fooid int, f2 int);
INSERT INTO foo2 VALUES(1, 11);
--- 13,19 ----
enable_seqscan | on
enable_sort | on
enable_tidscan | on
! (12 rows)
CREATE TABLE foo2(fooid int, f2 int);
INSERT INTO foo2 VALUES(1, 11);