diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
index 59cb053..07cd629 100644
--- a/contrib/postgres_fdw/deparse.c
+++ b/contrib/postgres_fdw/deparse.c
@@ -44,7 +44,9 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
+#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
+#include "nodes/plannodes.h"
#include "optimizer/clauses.h"
#include "optimizer/var.h"
#include "parser/parsetree.h"
@@ -60,7 +62,8 @@
typedef struct foreign_glob_cxt
{
PlannerInfo *root; /* global planner state */
- RelOptInfo *foreignrel; /* the foreign relation we are planning for */
+ RelOptInfo *outerrel; /* the foreign relation, or outer child */
+ RelOptInfo *innerrel; /* inner child, only set for join */
} foreign_glob_cxt;
/*
@@ -86,9 +89,12 @@ typedef struct foreign_loc_cxt
typedef struct deparse_expr_cxt
{
PlannerInfo *root; /* global planner state */
- RelOptInfo *foreignrel; /* the foreign relation we are planning for */
+ RelOptInfo *outerrel; /* the foreign relation, or outer child */
+ RelOptInfo *innerrel; /* inner child, only set for join */
StringInfo buf; /* output buffer to append to */
List **params_list; /* exprs that will become remote Params */
+ ForeignScan *outerplan; /* outer child's ForeignScan node */
+ ForeignScan *innerplan; /* inner child's ForeignScan node */
} deparse_expr_cxt;
/*
@@ -160,7 +166,7 @@ classifyConditions(PlannerInfo *root,
{
RestrictInfo *ri = (RestrictInfo *) lfirst(lc);
- if (is_foreign_expr(root, baserel, ri->clause))
+ if (is_foreign_expr(root, baserel, NULL, ri->clause))
*remote_conds = lappend(*remote_conds, ri);
else
*local_conds = lappend(*local_conds, ri);
@@ -172,7 +178,8 @@ classifyConditions(PlannerInfo *root,
*/
bool
is_foreign_expr(PlannerInfo *root,
- RelOptInfo *baserel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
Expr *expr)
{
foreign_glob_cxt glob_cxt;
@@ -183,7 +190,8 @@ is_foreign_expr(PlannerInfo *root,
* remotely.
*/
glob_cxt.root = root;
- glob_cxt.foreignrel = baserel;
+ glob_cxt.outerrel = outerrel;
+ glob_cxt.innerrel = innerrel;
loc_cxt.collation = InvalidOid;
loc_cxt.state = FDW_COLLATE_NONE;
if (!foreign_expr_walker((Node *) expr, &glob_cxt, &loc_cxt))
@@ -250,7 +258,7 @@ foreign_expr_walker(Node *node,
* Param's collation, ie it's not safe for it to have a
* non-default collation.
*/
- if (var->varno == glob_cxt->foreignrel->relid &&
+ if (var->varno == glob_cxt->outerrel->relid &&
var->varlevelsup == 0)
{
/* Var belongs to foreign table */
@@ -743,18 +751,22 @@ deparseTargetList(StringInfo buf,
if (attr->attisdropped)
continue;
+ if (!first)
+ appendStringInfoString(buf, ", ");
+ first = false;
+
if (have_wholerow ||
bms_is_member(i - FirstLowInvalidHeapAttributeNumber,
attrs_used))
{
- if (!first)
- appendStringInfoString(buf, ", ");
- first = false;
deparseColumnRef(buf, rtindex, i, root);
- *retrieved_attrs = lappend_int(*retrieved_attrs, i);
}
+ else
+ appendStringInfoString(buf, "NULL");
+
+ *retrieved_attrs = lappend_int(*retrieved_attrs, i);
}
/*
@@ -794,12 +806,15 @@ deparseTargetList(StringInfo buf,
* so Params and other-relation Vars should be replaced by dummy values.
*/
void
-appendWhereClause(StringInfo buf,
- PlannerInfo *root,
- RelOptInfo *baserel,
- List *exprs,
- bool is_first,
- List **params)
+appendConditions(StringInfo buf,
+ PlannerInfo *root,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ ForeignScan *outerplan,
+ ForeignScan *innerplan,
+ List *exprs,
+ const char *prefix,
+ List **params)
{
deparse_expr_cxt context;
int nestlevel;
@@ -810,9 +825,12 @@ appendWhereClause(StringInfo buf,
/* Set up context struct for recursion */
context.root = root;
- context.foreignrel = baserel;
+ context.outerrel = outerrel;
+ context.innerrel = innerrel;
context.buf = buf;
context.params_list = params;
+ context.outerplan = outerplan;
+ context.innerplan = innerplan;
/* Make sure any constants in the exprs are printed portably */
nestlevel = set_transmission_modes();
@@ -822,22 +840,149 @@ appendWhereClause(StringInfo buf,
RestrictInfo *ri = (RestrictInfo *) lfirst(lc);
/* Connect expressions with "AND" and parenthesize each condition. */
- if (is_first)
- appendStringInfoString(buf, " WHERE ");
- else
- appendStringInfoString(buf, " AND ");
+ if (prefix)
+ appendStringInfo(buf, "%s", prefix);
appendStringInfoChar(buf, '(');
deparseExpr(ri->clause, &context);
appendStringInfoChar(buf, ')');
- is_first = false;
+ prefix= " AND ";
}
reset_transmission_modes(nestlevel);
}
/*
+ * Deparse given Var into buf.
+ */
+static TargetEntry *
+deparseJoinVar(StringInfo buf, Var *var, Path *path_o, Path *path_i, List *tl_o, List *tl_i)
+{
+ List *targetlist;
+ const char *side;
+ ListCell *lc2;
+ TargetEntry *tle = NULL;
+ int j;
+
+ /* Find var from outer/inner subtree */
+ if (bms_is_member(var->varno, path_o->parent->relids))
+ {
+ targetlist = tl_o;
+ side = "l";
+ }
+ else if (bms_is_member(var->varno, path_i->parent->relids))
+ {
+ targetlist = tl_i;
+ side = "r";
+ }
+
+ j = 0;
+ foreach(lc2, targetlist)
+ {
+ TargetEntry *childtle = (TargetEntry *) lfirst(lc2);
+
+ if (equal(childtle->expr, var))
+ {
+ tle = copyObject(childtle);
+ break;
+ }
+ j++;
+ }
+ Assert(tle);
+
+ appendStringInfo(buf, "%s.a_%d", side, j);
+
+ return tle;
+}
+
+/*
+ * Construct a SELECT statement which contains join clause.
+ *
+ * We also create an TargetEntry List of the columns being retrieved, which is
+ * returned to *fdw_ps_tlist.
+ *
+ * path_o, tl_o, sql_o are respectively path, targetlist, and remote query
+ * statement of the outer child relation. postfix _i means those for the inner
+ * child relation. jointype and restrictlist are information of join method.
+ * fdw_ps_tlist is output parameter to pass target list of the pseudo scan to
+ * caller.
+ */
+void
+deparseJoinSql(StringInfo sql,
+ PlannerInfo *root,
+ RelOptInfo *baserel,
+ Path *path_o,
+ Path *path_i,
+ ForeignScan *plan_o,
+ ForeignScan *plan_i,
+ const char *sql_o,
+ const char *sql_i,
+ JoinType jointype,
+ List *restrictlist,
+ List **fdw_ps_tlist)
+{
+ StringInfoData selbuf; /* buffer for SELECT clause */
+ StringInfoData abuf_o; /* buffer for column alias list of outer */
+ StringInfoData abuf_i; /* buffer for column alias list of inner */
+ int i;
+ ListCell *lc;
+ const char *jointype_str;
+
+ jointype_str = jointype == JOIN_INNER ? "INNER" :
+ jointype == JOIN_LEFT ? "LEFT" :
+ jointype == JOIN_RIGHT ? "RIGHT" :
+ jointype == JOIN_FULL ? "FULL" : "";
+
+ /* print SELECT clause of the join scan */
+ /* XXX: should extend deparseTargetList()? */
+ initStringInfo(&selbuf);
+ i = 0;
+ foreach(lc, baserel->reltargetlist)
+ {
+ Var *var = (Var *) lfirst(lc);
+ TargetEntry *tle;
+
+ if (i > 0)
+ appendStringInfoString(&selbuf, ", ");
+ deparseJoinVar(&selbuf, var, path_o, path_i,
+ plan_o->scan.plan.targetlist,
+ plan_i->scan.plan.targetlist);
+
+ tle = makeTargetEntry((Expr *) copyObject(var),
+ i + 1, pstrdup(""), false);
+ if (fdw_ps_tlist)
+ *fdw_ps_tlist = lappend(*fdw_ps_tlist, copyObject(tle));
+
+ i++;
+ }
+
+ /* Deparse column alias portion of subquery in FROM clause. */
+ initStringInfo(&abuf_o);
+ initStringInfo(&abuf_i);
+ for (i = 0; i < list_length(plan_o->scan.plan.targetlist); i++)
+ {
+ if (i > 0)
+ appendStringInfoString(&abuf_o, ", ");
+ appendStringInfo(&abuf_o, "a_%d", i);
+ }
+ for (i = 0; i < list_length(plan_i->scan.plan.targetlist); i++)
+ {
+ if (i > 0)
+ appendStringInfoString(&abuf_i, ", ");
+ appendStringInfo(&abuf_i, "a_%d", i);
+ }
+
+ /* Construct SELECT statement */
+ appendStringInfo(sql, "SELECT %s FROM", selbuf.data);
+ appendStringInfo(sql, " (%s) l (%s) %s JOIN (%s) r (%s) ",
+ sql_o, abuf_o.data, jointype_str, sql_i, abuf_i.data);
+ /* Append ON clause */
+ appendConditions(sql, root, path_o->parent, path_i->parent, plan_o, plan_i,
+ restrictlist, " ON ", NULL);
+}
+
+/*
* deparse remote INSERT statement
*
* The statement text is appended to buf, and we also create an integer List
@@ -1261,6 +1406,8 @@ deparseExpr(Expr *node, deparse_expr_cxt *context)
/*
* Deparse given Var node into context->buf.
*
+ * If context has valid innerrel, this is invoked for a join conditions.
+ *
* If the Var belongs to the foreign relation, just print its remote name.
* Otherwise, it's effectively a Param (and will in fact be a Param at
* run time). Handle it the same way we handle plain Params --- see
@@ -1271,39 +1418,50 @@ deparseVar(Var *node, deparse_expr_cxt *context)
{
StringInfo buf = context->buf;
- if (node->varno == context->foreignrel->relid &&
- node->varlevelsup == 0)
+ if (context->innerrel != NULL)
{
- /* Var belongs to foreign table */
- deparseColumnRef(buf, node->varno, node->varattno, context->root);
+ deparseJoinVar(context->buf, node,
+ context->outerrel->cheapest_total_path,
+ context->innerrel->cheapest_total_path,
+ context->outerplan->scan.plan.targetlist,
+ context->innerplan->scan.plan.targetlist);
}
else
{
- /* Treat like a Param */
- if (context->params_list)
+ if (node->varno == context->outerrel->relid &&
+ node->varlevelsup == 0)
{
- int pindex = 0;
- ListCell *lc;
-
- /* find its index in params_list */
- foreach(lc, *context->params_list)
+ /* Var belongs to foreign table */
+ deparseColumnRef(buf, node->varno, node->varattno, context->root);
+ }
+ else
+ {
+ /* Treat like a Param */
+ if (context->params_list)
{
- pindex++;
- if (equal(node, (Node *) lfirst(lc)))
- break;
+ int pindex = 0;
+ ListCell *lc;
+
+ /* find its index in params_list */
+ foreach(lc, *context->params_list)
+ {
+ pindex++;
+ if (equal(node, (Node *) lfirst(lc)))
+ break;
+ }
+ if (lc == NULL)
+ {
+ /* not in list, so add it */
+ pindex++;
+ *context->params_list = lappend(*context->params_list, node);
+ }
+
+ printRemoteParam(pindex, node->vartype, node->vartypmod, context);
}
- if (lc == NULL)
+ else
{
- /* not in list, so add it */
- pindex++;
- *context->params_list = lappend(*context->params_list, node);
+ printRemotePlaceholder(node->vartype, node->vartypmod, context);
}
-
- printRemoteParam(pindex, node->vartype, node->vartypmod, context);
- }
- else
- {
- printRemotePlaceholder(node->vartype, node->vartypmod, context);
}
}
}
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 583cce7..92d9b1f 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -656,16 +656,16 @@ SELECT * FROM ft2 WHERE c1 = ANY (ARRAY(SELECT c1 FROM ft1 WHERE c1 < 5));
-- simple join
PREPARE st1(int, int) AS SELECT t1.c3, t2.c3 FROM ft1 t1, ft2 t2 WHERE t1.c1 = $1 AND t2.c1 = $2;
EXPLAIN (VERBOSE, COSTS false) EXECUTE st1(1, 2);
- QUERY PLAN
---------------------------------------------------------------------
+ QUERY PLAN
+--------------------------------------------------------------------------------------------------------------
Nested Loop
Output: t1.c3, t2.c3
-> Foreign Scan on public.ft1 t1
Output: t1.c3
- Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE (("C 1" = 1))
+ Remote SQL: SELECT NULL, NULL, c3, NULL, NULL, NULL, NULL, NULL FROM "S 1"."T 1" WHERE (("C 1" = 1))
-> Foreign Scan on public.ft2 t2
Output: t2.c3
- Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE (("C 1" = 2))
+ Remote SQL: SELECT NULL, NULL, c3, NULL, NULL, NULL, NULL, NULL FROM "S 1"."T 1" WHERE (("C 1" = 2))
(8 rows)
EXECUTE st1(1, 1);
@@ -683,8 +683,8 @@ EXECUTE st1(101, 101);
-- subquery using stable function (can't be sent to remote)
PREPARE st2(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND date(c4) = '1970-01-17'::date) ORDER BY c1;
EXPLAIN (VERBOSE, COSTS false) EXECUTE st2(10, 20);
- QUERY PLAN
-----------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+-------------------------------------------------------------------------------------------------------------------------
Sort
Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8
Sort Key: t1.c1
@@ -699,7 +699,7 @@ EXPLAIN (VERBOSE, COSTS false) EXECUTE st2(10, 20);
-> Foreign Scan on public.ft2 t2
Output: t2.c3
Filter: (date(t2.c4) = '01-17-1970'::date)
- Remote SQL: SELECT c3, c4 FROM "S 1"."T 1" WHERE (("C 1" > 10))
+ Remote SQL: SELECT NULL, NULL, c3, c4, NULL, NULL, NULL, NULL FROM "S 1"."T 1" WHERE (("C 1" > 10))
(15 rows)
EXECUTE st2(10, 20);
@@ -717,8 +717,8 @@ EXECUTE st2(101, 121);
-- subquery using immutable function (can be sent to remote)
PREPARE st3(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND date(c5) = '1970-01-17'::date) ORDER BY c1;
EXPLAIN (VERBOSE, COSTS false) EXECUTE st3(10, 20);
- QUERY PLAN
------------------------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------
Sort
Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8
Sort Key: t1.c1
@@ -732,7 +732,7 @@ EXPLAIN (VERBOSE, COSTS false) EXECUTE st3(10, 20);
Output: t2.c3
-> Foreign Scan on public.ft2 t2
Output: t2.c3
- Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE (("C 1" > 10)) AND ((date(c5) = '1970-01-17'::date))
+ Remote SQL: SELECT NULL, NULL, c3, NULL, NULL, NULL, NULL, NULL FROM "S 1"."T 1" WHERE (("C 1" > 10)) AND ((date(c5) = '1970-01-17'::date))
(14 rows)
EXECUTE st3(10, 20);
@@ -1085,7 +1085,7 @@ INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20;
Output: ((ft2_1.c1 + 1000)), ((ft2_1.c2 + 100)), ((ft2_1.c3 || ft2_1.c3))
-> Foreign Scan on public.ft2 ft2_1
Output: (ft2_1.c1 + 1000), (ft2_1.c2 + 100), (ft2_1.c3 || ft2_1.c3)
- Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1"
+ Remote SQL: SELECT "C 1", c2, c3, NULL, NULL, NULL, NULL, NULL FROM "S 1"."T 1"
(9 rows)
INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20;
@@ -1219,7 +1219,7 @@ UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT
Hash Cond: (ft2.c2 = ft1.c1)
-> Foreign Scan on public.ft2
Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c8, ft2.ctid
- Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c8, ctid FROM "S 1"."T 1" FOR UPDATE
+ Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, NULL, c8, ctid FROM "S 1"."T 1" FOR UPDATE
-> Hash
Output: ft1.*, ft1.c1
-> Foreign Scan on public.ft1
@@ -1231,14 +1231,14 @@ UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT
FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9;
EXPLAIN (verbose, costs off)
DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4;
- QUERY PLAN
-----------------------------------------------------------------------------------------
+ QUERY PLAN
+----------------------------------------------------------------------------------------------------------------------------------------
Delete on public.ft2
Output: c1, c4
- Remote SQL: DELETE FROM "S 1"."T 1" WHERE ctid = $1 RETURNING "C 1", c4
+ Remote SQL: DELETE FROM "S 1"."T 1" WHERE ctid = $1 RETURNING "C 1", NULL, NULL, c4, NULL, NULL, NULL, NULL
-> Foreign Scan on public.ft2
Output: ctid
- Remote SQL: SELECT ctid FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 5)) FOR UPDATE
+ Remote SQL: SELECT NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, ctid FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 5)) FOR UPDATE
(6 rows)
DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4;
@@ -1360,7 +1360,7 @@ DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2;
Hash Cond: (ft2.c2 = ft1.c1)
-> Foreign Scan on public.ft2
Output: ft2.ctid, ft2.c2
- Remote SQL: SELECT c2, ctid FROM "S 1"."T 1" FOR UPDATE
+ Remote SQL: SELECT NULL, c2, NULL, NULL, NULL, NULL, NULL, NULL, ctid FROM "S 1"."T 1" FOR UPDATE
-> Hash
Output: ft1.*, ft1.c1
-> Foreign Scan on public.ft1
@@ -2594,12 +2594,12 @@ select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1;
-- Consistent check constraints provide consistent results
ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2positive CHECK (c2 >= 0);
EXPLAIN (VERBOSE, COSTS false) SELECT count(*) FROM ft1 WHERE c2 < 0;
- QUERY PLAN
--------------------------------------------------------------------
+ QUERY PLAN
+-------------------------------------------------------------------------------------------------------------
Aggregate
Output: count(*)
-> Foreign Scan on public.ft1
- Remote SQL: SELECT NULL FROM "S 1"."T 1" WHERE ((c2 < 0))
+ Remote SQL: SELECT NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL FROM "S 1"."T 1" WHERE ((c2 < 0))
(4 rows)
SELECT count(*) FROM ft1 WHERE c2 < 0;
@@ -2638,12 +2638,12 @@ ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2positive;
-- But inconsistent check constraints provide inconsistent results
ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2negative CHECK (c2 < 0);
EXPLAIN (VERBOSE, COSTS false) SELECT count(*) FROM ft1 WHERE c2 >= 0;
- QUERY PLAN
---------------------------------------------------------------------
+ QUERY PLAN
+--------------------------------------------------------------------------------------------------------------
Aggregate
Output: count(*)
-> Foreign Scan on public.ft1
- Remote SQL: SELECT NULL FROM "S 1"."T 1" WHERE ((c2 >= 0))
+ Remote SQL: SELECT NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL FROM "S 1"."T 1" WHERE ((c2 >= 0))
(4 rows)
SELECT count(*) FROM ft1 WHERE c2 >= 0;
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 63f0577..16df515 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
{
@@ -78,10 +79,30 @@ typedef struct PgFdwRelationInfo
ForeignTable *table;
ForeignServer *server;
UserMapping *user; /* only set in use_remote_estimate mode */
+ Oid checkAsUser;
} PgFdwRelationInfo;
/*
- * Indexes of FDW-private information stored in fdw_private lists.
+ * Indexes of FDW-private information stored in fdw_private of ForeignPath.
+ * We use fdw_private of a ForeighPath when the path represents a join which
+ * can be pushed down to remote side.
+ *
+ * 1) Outer child path node
+ * 2) Inner child path node
+ * 3) Join type number(as an Integer node)
+ * 4) RestrictInfo list of join conditions
+ */
+enum FdwPathPrivateIndex
+{
+ FdwPathPrivateOuterPath,
+ FdwPathPrivateInnerPath,
+ FdwPathPrivateJoinType,
+ FdwPathPrivateRestrictList,
+};
+
+/*
+ * Indexes of FDW-private information stored in fdw_private of ForeignScan of
+ * a simple foreign table scan for a SELECT statement.
*
* We store various information in ForeignScan.fdw_private to pass it from
* planner to executor. Currently we store:
@@ -98,7 +119,11 @@ enum FdwScanPrivateIndex
/* SQL statement to execute remotely (as a String node) */
FdwScanPrivateSelectSql,
/* Integer list of attribute numbers retrieved by the SELECT */
- FdwScanPrivateRetrievedAttrs
+ FdwScanPrivateRetrievedAttrs,
+ /* Integer value of server for the scan */
+ FdwScanPrivateServerOid,
+ /* Integer value of checkAsUser for the scan */
+ FdwScanPrivatecheckAsUser,
};
/*
@@ -129,6 +154,7 @@ enum FdwModifyPrivateIndex
typedef struct PgFdwScanState
{
Relation rel; /* relcache entry for the foreign table */
+ TupleDesc tupdesc; /* tuple descriptor of the scan */
AttInMetadata *attinmeta; /* attribute datatype conversion metadata */
/* extracted fdw_private data */
@@ -288,6 +314,15 @@ 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);
/*
* Helper functions
@@ -324,6 +359,7 @@ static void analyze_row_processor(PGresult *res, int row,
static HeapTuple make_tuple_from_result_row(PGresult *res,
int row,
Relation rel,
+ TupleDesc tupdesc,
AttInMetadata *attinmeta,
List *retrieved_attrs,
MemoryContext temp_context);
@@ -368,6 +404,9 @@ postgres_fdw_handler(PG_FUNCTION_ARGS)
/* Support functions for IMPORT FOREIGN SCHEMA */
routine->ImportForeignSchema = postgresImportForeignSchema;
+ /* Support functions for join push-down */
+ routine->GetForeignJoinPath = postgresGetForeignJoinPath;
+
PG_RETURN_POINTER(routine);
}
@@ -385,6 +424,7 @@ postgresGetForeignRelSize(PlannerInfo *root,
{
PgFdwRelationInfo *fpinfo;
ListCell *lc;
+ RangeTblEntry *rte;
/*
* We use PgFdwRelationInfo to pass various information to subsequent
@@ -428,6 +468,13 @@ postgresGetForeignRelSize(PlannerInfo *root,
}
/*
+ * Retrieve RTE to obtain checkAsUser. checkAsUser is used to determine
+ * the user to use to obtain user mapping.
+ */
+ rte = planner_rt_fetch(baserel->relid, root);
+ fpinfo->checkAsUser = rte->checkAsUser;
+
+ /*
* If the table or the server is configured to use remote estimates,
* identify which user to do remote access as during planning. This
* should match what ExecCheckRTEPerms() does. If we fail due to lack of
@@ -435,7 +482,6 @@ postgresGetForeignRelSize(PlannerInfo *root,
*/
if (fpinfo->use_remote_estimate)
{
- RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root);
Oid userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
fpinfo->user = GetUserMapping(userid, fpinfo->server->serverid);
@@ -596,7 +642,7 @@ postgresGetForeignPaths(PlannerInfo *root,
continue;
/* See if it is safe to send to remote */
- if (!is_foreign_expr(root, baserel, rinfo->clause))
+ if (!is_foreign_expr(root, baserel, NULL, rinfo->clause))
continue;
/* Calculate required outer rels for the resulting path */
@@ -672,7 +718,8 @@ postgresGetForeignPaths(PlannerInfo *root,
continue;
/* See if it is safe to send to remote */
- if (!is_foreign_expr(root, baserel, rinfo->clause))
+ if (!is_foreign_expr(root, baserel, NULL,
+ rinfo->clause))
continue;
/* Calculate required outer rels for the resulting path */
@@ -752,6 +799,8 @@ postgresGetForeignPlan(PlannerInfo *root,
List *retrieved_attrs;
StringInfoData sql;
ListCell *lc;
+ List *fdw_ps_tlist = NIL;
+ ForeignScan *scan;
/*
* Separate the scan_clauses into those that can be executed remotely and
@@ -769,7 +818,7 @@ postgresGetForeignPlan(PlannerInfo *root,
* This code must match "extract_actual_clauses(scan_clauses, false)"
* except for the additional decision about remote versus local execution.
* Note however that we only strip the RestrictInfo nodes from the
- * local_exprs list, since appendWhereClause expects a list of
+ * local_exprs list, since appendConditions expects a list of
* RestrictInfos.
*/
foreach(lc, scan_clauses)
@@ -786,7 +835,7 @@ postgresGetForeignPlan(PlannerInfo *root,
remote_conds = lappend(remote_conds, rinfo);
else if (list_member_ptr(fpinfo->local_conds, rinfo))
local_exprs = lappend(local_exprs, rinfo->clause);
- else if (is_foreign_expr(root, baserel, rinfo->clause))
+ else if (is_foreign_expr(root, baserel, NULL, rinfo->clause))
remote_conds = lappend(remote_conds, rinfo);
else
local_exprs = lappend(local_exprs, rinfo->clause);
@@ -797,64 +846,123 @@ postgresGetForeignPlan(PlannerInfo *root,
* expressions to be sent as parameters.
*/
initStringInfo(&sql);
- deparseSelectSql(&sql, root, baserel, fpinfo->attrs_used,
- &retrieved_attrs);
- if (remote_conds)
- appendWhereClause(&sql, root, baserel, remote_conds,
- true, ¶ms_list);
-
- /*
- * Add FOR UPDATE/SHARE if appropriate. We apply locking during the
- * initial row fetch, rather than later on as is done for local tables.
- * The extra roundtrips involved in trying to duplicate the local
- * semantics exactly don't seem worthwhile (see also comments for
- * RowMarkType).
- *
- * Note: because we actually run the query as a cursor, this assumes that
- * DECLARE CURSOR ... FOR UPDATE is supported, which it isn't before 8.3.
- */
- if (baserel->relid == root->parse->resultRelation &&
- (root->parse->commandType == CMD_UPDATE ||
- root->parse->commandType == CMD_DELETE))
+ if (scan_relid > 0)
{
- /* Relation is UPDATE/DELETE target, so use FOR UPDATE */
- appendStringInfoString(&sql, " FOR UPDATE");
- }
- else
- {
- RowMarkClause *rc = get_parse_rowmark(root->parse, baserel->relid);
+ deparseSelectSql(&sql, root, baserel, fpinfo->attrs_used,
+ &retrieved_attrs);
+ if (remote_conds)
+ appendConditions(&sql, root, baserel, NULL, NULL, NULL,
+ remote_conds, " WHERE ", ¶ms_list);
- if (rc)
+ /*
+ * Add FOR UPDATE/SHARE if appropriate. We apply locking during the
+ * initial row fetch, rather than later on as is done for local tables.
+ * The extra roundtrips involved in trying to duplicate the local
+ * semantics exactly don't seem worthwhile (see also comments for
+ * RowMarkType).
+ *
+ * Note: because we actually run the query as a cursor, this assumes
+ * that DECLARE CURSOR ... FOR UPDATE is supported, which it isn't
+ * before 8.3.
+ */
+ if (baserel->relid == root->parse->resultRelation &&
+ (root->parse->commandType == CMD_UPDATE ||
+ root->parse->commandType == CMD_DELETE))
{
- /*
- * Relation is specified as a FOR UPDATE/SHARE target, so handle
- * that.
- *
- * For now, just ignore any [NO] KEY specification, since (a) it's
- * not clear what that means for a remote table that we don't have
- * complete information about, and (b) it wouldn't work anyway on
- * older remote servers. Likewise, we don't worry about NOWAIT.
- */
- switch (rc->strength)
+ /* Relation is UPDATE/DELETE target, so use FOR UPDATE */
+ appendStringInfoString(&sql, " FOR UPDATE");
+ }
+ else
+ {
+ RowMarkClause *rc = get_parse_rowmark(root->parse, baserel->relid);
+
+ if (rc)
{
- case LCS_FORKEYSHARE:
- case LCS_FORSHARE:
- appendStringInfoString(&sql, " FOR SHARE");
- break;
- case LCS_FORNOKEYUPDATE:
- case LCS_FORUPDATE:
- appendStringInfoString(&sql, " FOR UPDATE");
- break;
+ /*
+ * Relation is specified as a FOR UPDATE/SHARE target, so handle
+ * that.
+ *
+ * For now, just ignore any [NO] KEY specification, since (a)
+ * it's not clear what that means for a remote table that we
+ * don't have complete information about, and (b) it wouldn't
+ * work anyway on older remote servers. Likewise, we don't
+ * worry about NOWAIT.
+ */
+ switch (rc->strength)
+ {
+ case LCS_FORKEYSHARE:
+ case LCS_FORSHARE:
+ appendStringInfoString(&sql, " FOR SHARE");
+ break;
+ case LCS_FORNOKEYUPDATE:
+ case LCS_FORUPDATE:
+ appendStringInfoString(&sql, " FOR UPDATE");
+ break;
+ }
}
}
}
+ else
+ {
+ /* Join case */
+ Path *path_o;
+ Path *path_i;
+ const char *sql_o;
+ const char *sql_i;
+ ForeignScan *plan_o;
+ ForeignScan *plan_i;
+ JoinType jointype;
+ List *restrictlist;
+ int i;
+
+ /*
+ * Retrieve infomation from fdw_private.
+ */
+ path_o = list_nth(best_path->fdw_private, FdwPathPrivateOuterPath);
+ path_i = list_nth(best_path->fdw_private, FdwPathPrivateInnerPath);
+ jointype = intVal(list_nth(best_path->fdw_private,
+ FdwPathPrivateJoinType));
+ restrictlist = list_nth(best_path->fdw_private,
+ FdwPathPrivateRestrictList);
+
+ /*
+ * Construct remote query from bottom to the top. ForeignScan plan
+ * node of underlying scans are node necessary for execute the plan
+ * tree, but it is handy to construct remote query recursively.
+ */
+ plan_o = (ForeignScan *) create_plan_recurse(root, path_o);
+ Assert(IsA(plan_o, ForeignScan));
+ sql_o = strVal(list_nth(plan_o->fdw_private, FdwScanPrivateSelectSql));
+
+ plan_i = (ForeignScan *) create_plan_recurse(root, path_i);
+ Assert(IsA(plan_i, ForeignScan));
+ sql_i = strVal(list_nth(plan_i->fdw_private, FdwScanPrivateSelectSql));
+
+ deparseJoinSql(&sql, root, baserel, path_o, path_i, plan_o, plan_i,
+ sql_o, sql_i, jointype, restrictlist, &fdw_ps_tlist);
+ retrieved_attrs = NIL;
+ for (i = 0; i < list_length(fdw_ps_tlist); i++)
+ retrieved_attrs = lappend_int(retrieved_attrs, i + 1);
+ }
/*
* Build the fdw_private list that will be available to the executor.
* Items in the list must match enum FdwScanPrivateIndex, above.
*/
- fdw_private = list_make2(makeString(sql.data),
- retrieved_attrs);
+ fdw_private = list_make2(makeString(sql.data), retrieved_attrs);
+
+ /*
+ * In pseudo scan case such as join push-down, add OID of server and
+ * checkAsUser as extra information.
+ * XXX: passing serverid and checkAsUser might simplify code through
+ * all cases, simple scans and join push-down.
+ */
+ if (scan_relid == 0)
+ {
+ fdw_private = lappend(fdw_private,
+ makeInteger(fpinfo->server->serverid));
+ fdw_private = lappend(fdw_private, makeInteger(fpinfo->checkAsUser));
+ }
/*
* Create the ForeignScan node from target list, local filtering
@@ -864,11 +972,18 @@ postgresGetForeignPlan(PlannerInfo *root,
* field of the finished plan node; we can't keep them in private state
* because then they wouldn't be subject to later planner processing.
*/
- return make_foreignscan(tlist,
+ scan = make_foreignscan(tlist,
local_exprs,
scan_relid,
params_list,
fdw_private);
+
+ /*
+ * set fdw_ps_tlist to handle tuples generated by this scan.
+ */
+ scan->fdw_ps_tlist = fdw_ps_tlist;
+
+ return scan;
}
/*
@@ -881,9 +996,8 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
ForeignScan *fsplan = (ForeignScan *) node->ss.ps.plan;
EState *estate = node->ss.ps.state;
PgFdwScanState *fsstate;
- RangeTblEntry *rte;
+ Oid serverid;
Oid userid;
- ForeignTable *table;
ForeignServer *server;
UserMapping *user;
int numParams;
@@ -903,22 +1017,51 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
node->fdw_state = (void *) fsstate;
/*
- * Identify which user to do the remote access as. This should match what
- * ExecCheckRTEPerms() does.
+ * Initialize fsstate.
+ *
+ * These values should be determined.
+ * - fsstate->rel, NULL if no actual relation
+ * - serverid, OID of forign server to use for the scan
+ * - userid, searching user mapping
*/
- rte = rt_fetch(fsplan->scan.scanrelid, estate->es_range_table);
- userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+ if (fsplan->scan.scanrelid > 0)
+ {
+ /* Simple foreign table scan */
+ RangeTblEntry *rte;
+ ForeignTable *table;
- /* 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);
+ /*
+ * 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();
+
+ /* Get info about foreign table. */
+ fsstate->rel = node->ss.ss_currentRelation;
+ table = GetForeignTable(RelationGetRelid(fsstate->rel));
+ serverid = table->serverid;
+ }
+ else
+ {
+ Oid checkAsUser;
+
+ /* Join */
+ fsstate->rel = NULL; /* No actual relation to scan */
+
+ serverid = intVal(list_nth(fsplan->fdw_private,
+ FdwScanPrivateServerOid));
+ checkAsUser = intVal(list_nth(fsplan->fdw_private,
+ FdwScanPrivatecheckAsUser));
+ userid = checkAsUser ? checkAsUser : GetUserId();
+ }
/*
* Get connection to the foreign server. Connection manager will
* establish new connection if necessary.
*/
+ server = GetForeignServer(serverid);
+ user = GetUserMapping(userid, server->serverid);
fsstate->conn = GetConnection(server, user, false);
/* Assign a unique ID for my cursor */
@@ -929,7 +1072,7 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
fsstate->query = strVal(list_nth(fsplan->fdw_private,
FdwScanPrivateSelectSql));
fsstate->retrieved_attrs = (List *) list_nth(fsplan->fdw_private,
- FdwScanPrivateRetrievedAttrs);
+ FdwScanPrivateRetrievedAttrs);
/* Create contexts for batches of tuples and per-tuple temp workspace. */
fsstate->batch_cxt = AllocSetContextCreate(estate->es_query_cxt,
@@ -944,7 +1087,11 @@ 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->tupdesc = RelationGetDescr(fsstate->rel);
+ else
+ fsstate->tupdesc = node->ss.ss_ScanTupleSlot->tts_tupleDescriptor;
+ fsstate->attinmeta = TupleDescGetAttInMetadata(fsstate->tupdesc);
/* Prepare for output conversion of parameters used in remote query. */
numParams = list_length(fsplan->fdw_exprs);
@@ -1747,11 +1894,13 @@ estimate_path_cost_size(PlannerInfo *root,
deparseSelectSql(&sql, root, baserel, fpinfo->attrs_used,
&retrieved_attrs);
if (fpinfo->remote_conds)
- appendWhereClause(&sql, root, baserel, fpinfo->remote_conds,
- true, NULL);
+ appendConditions(&sql, root, baserel, NULL, NULL, NULL,
+ fpinfo->remote_conds, " WHERE ", NULL);
if (remote_join_conds)
- appendWhereClause(&sql, root, baserel, remote_join_conds,
- (fpinfo->remote_conds == NIL), NULL);
+ appendConditions(&sql, root, baserel, NULL, NULL, NULL,
+ remote_join_conds,
+ fpinfo->remote_conds == NIL ? " WHERE " : " AND ",
+ NULL);
/* Get the remote estimate */
conn = GetConnection(fpinfo->server, fpinfo->user, false);
@@ -2052,6 +2201,7 @@ fetch_more_data(ForeignScanState *node)
fsstate->tuples[i] =
make_tuple_from_result_row(res, i,
fsstate->rel,
+ fsstate->tupdesc,
fsstate->attinmeta,
fsstate->retrieved_attrs,
fsstate->temp_cxt);
@@ -2270,6 +2420,7 @@ store_returning_result(PgFdwModifyState *fmstate,
newtup = make_tuple_from_result_row(res, 0,
fmstate->rel,
+ NULL,
fmstate->attinmeta,
fmstate->retrieved_attrs,
fmstate->temp_cxt);
@@ -2562,6 +2713,7 @@ analyze_row_processor(PGresult *res, int row, PgFdwAnalyzeState *astate)
astate->rows[pos] = make_tuple_from_result_row(res, row,
astate->rel,
+ NULL,
astate->attinmeta,
astate->retrieved_attrs,
astate->temp_cxt);
@@ -2835,6 +2987,181 @@ postgresImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid)
}
/*
+ * Construct PgFdwRelationInfo from two join sources
+ */
+static PgFdwRelationInfo *
+merge_fpinfo(PgFdwRelationInfo *fpinfo_o,
+ PgFdwRelationInfo *fpinfo_i,
+ JoinType jointype)
+{
+ PgFdwRelationInfo *fpinfo;
+
+ fpinfo = (PgFdwRelationInfo *) palloc0(sizeof(PgFdwRelationInfo));
+ fpinfo->remote_conds = list_concat(copyObject(fpinfo_o->remote_conds),
+ copyObject(fpinfo_i->remote_conds));
+ fpinfo->local_conds = list_concat(copyObject(fpinfo_o->local_conds),
+ copyObject(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;
+ if (jointype == JOIN_INNER)
+ fpinfo->rows = Min(fpinfo_o->rows, fpinfo_i->rows);
+ else
+ fpinfo->rows = Max(fpinfo_o->rows, fpinfo_i->rows);
+ 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;
+ /* checkAsuser must be identical */
+ fpinfo->checkAsUser = fpinfo_o->checkAsUser;
+
+ return fpinfo;
+}
+
+/*
+ * postgresGetForeignJoinPath
+ * Add possible ForeignPath to joinrel.
+ *
+ * Joins satify conditions below can be pushed down to remote PostgreSQL server.
+ *
+ * 1) Join type is inner or outer
+ * 2) Join conditions consist of remote-safe expressions.
+ * 3) Join source relations don't have any local filter.
+ */
+static void
+postgresGetForeignJoinPath(PlannerInfo *root,
+ RelOptInfo *joinrel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ JoinType jointype,
+ SpecialJoinInfo *sjinfo,
+ SemiAntiJoinFactors *semifactors,
+ List *restrictlist,
+ Relids extra_lateral_rels)
+{
+ ForeignPath *joinpath;
+ ForeignPath *path_o = (ForeignPath *) outerrel->cheapest_total_path;
+ ForeignPath *path_i = (ForeignPath *) innerrel->cheapest_total_path;
+ PgFdwRelationInfo *fpinfo_o;
+ PgFdwRelationInfo *fpinfo_i;
+ PgFdwRelationInfo *fpinfo;
+ double rows;
+ Cost startup_cost;
+ Cost total_cost;
+ ListCell *lc;
+ List *fdw_private;
+
+ /* Source relations should be ForeignPath. */
+ if (!IsA(path_o, ForeignPath) || !IsA(path_i, ForeignPath))
+ return;
+
+ /*
+ * Skip considering reversed join combination.
+ */
+ if (outerrel->relid < innerrel->relid)
+ return;
+
+ /*
+ * Both relations in the join must belong to same server.
+ */
+ fpinfo_o = path_o->path.parent->fdw_private;
+ fpinfo_i = path_i->path.parent->fdw_private;
+ if (fpinfo_o->server->serverid != fpinfo_i->server->serverid)
+ 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 == NIL)
+ return;
+
+ /*
+ * Neither source relation can have local conditions. This can be relaxed
+ * if the join is an inner join and local conditions don't contain volatile
+ * function/operator, but as of now we leave it as future enhancement.
+ */
+ if (fpinfo_o->local_conds != NULL || fpinfo_i->local_conds != NULL)
+ return;
+
+ /*
+ * Join condition must be safe to push down.
+ */
+ foreach(lc, restrictlist)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ if (!is_foreign_expr(root, joinrel, NULL, rinfo->clause))
+ return;
+ }
+
+ /*
+ * checkAsUser of source pathes should match.
+ */
+ if (fpinfo_o->checkAsUser != fpinfo_i->checkAsUser)
+ return;
+
+ /* Here we know that this join can be pushed-down to remote side. */
+
+ /* Construct fpinfo for the join relation */
+ fpinfo = merge_fpinfo(fpinfo_o, fpinfo_i, jointype);
+ joinrel->fdw_private = fpinfo;
+
+ /* TODO determine cost and rows of the join. */
+ rows = fpinfo->rows;
+ startup_cost = fpinfo->startup_cost;
+ total_cost = fpinfo->total_cost;
+
+ fdw_private = list_make4(path_o,
+ path_i,
+ makeInteger(jointype),
+ restrictlist);
+
+ /*
+ * Create a new join path and add it to the joinrel which represents a join
+ * between foreign tables.
+ */
+ joinpath = create_foreignscan_path(root,
+ joinrel,
+ rows,
+ startup_cost,
+ total_cost,
+ NIL, /* no pathkeys */
+ NULL, /* no required_outer */
+ fdw_private);
+
+ /* Add generated path into joinrel by add_path(). */
+ add_path(joinrel, (Path *) joinpath);
+
+ /* TODO consider parameterized paths */
+}
+
+/*
* Create a tuple from the specified row of the PGresult.
*
* rel is the local representation of the foreign table, attinmeta is
@@ -2846,12 +3173,12 @@ static HeapTuple
make_tuple_from_result_row(PGresult *res,
int row,
Relation rel,
+ TupleDesc tupdesc,
AttInMetadata *attinmeta,
List *retrieved_attrs,
MemoryContext temp_context)
{
HeapTuple tuple;
- TupleDesc tupdesc = RelationGetDescr(rel);
Datum *values;
bool *nulls;
ItemPointer ctid = NULL;
diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h
index 950c6f7..23a6b24 100644
--- a/contrib/postgres_fdw/postgres_fdw.h
+++ b/contrib/postgres_fdw/postgres_fdw.h
@@ -16,6 +16,7 @@
#include "foreign/foreign.h"
#include "lib/stringinfo.h"
#include "nodes/relation.h"
+#include "nodes/plannodes.h"
#include "utils/relcache.h"
#include "libpq-fe.h"
@@ -45,19 +46,35 @@ extern void classifyConditions(PlannerInfo *root,
List **remote_conds,
List **local_conds);
extern bool is_foreign_expr(PlannerInfo *root,
- RelOptInfo *baserel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
Expr *expr);
extern void deparseSelectSql(StringInfo buf,
PlannerInfo *root,
RelOptInfo *baserel,
Bitmapset *attrs_used,
List **retrieved_attrs);
-extern void appendWhereClause(StringInfo buf,
+extern void appendConditions(StringInfo buf,
PlannerInfo *root,
- RelOptInfo *baserel,
+ RelOptInfo *outerrel,
+ RelOptInfo *innerrel,
+ ForeignScan *outerplan,
+ ForeignScan *innerplan,
List *exprs,
- bool is_first,
+ const char *prefix,
List **params);
+extern void deparseJoinSql(StringInfo sql,
+ PlannerInfo *root,
+ RelOptInfo *baserel,
+ Path *path_o,
+ Path *path_i,
+ ForeignScan *plan_o,
+ ForeignScan *plan_i,
+ const char *sql_o,
+ const char *sql_i,
+ JoinType jointype,
+ List *restrictlist,
+ List **retrieved_attrs);
extern void deparseInsertSql(StringInfo buf, PlannerInfo *root,
Index rtindex, Relation rel,
List *targetAttrs, List *returningList,
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 9261e7f..7abc8e2 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -3071,6 +3071,20 @@ include_dir 'conf.d'
+
+ enable_foiregnjoin (boolean)
+
+ enable_foiregnjoin> configuration parameter
+
+
+
+
+ Enables or disables the query planner's use of foreign-scan plan
+ types for joining foreign tables. The default is on>.
+
+
+
+
enable_hashagg (boolean)
@@ -7687,6 +7701,7 @@ LOG: CleanUpLock: deleting: lock(0xb7acd844) id(24688,24696,0,0,0,1)
enable_bitmapscan = off>,
+ enable_foreignjoin = off>,
enable_hashjoin = off>,
enable_indexscan = off>,
enable_mergejoin = off>,
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 78ef229..b1d2683 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -117,6 +117,7 @@ bool enable_nestloop = true;
bool enable_material = true;
bool enable_mergejoin = true;
bool enable_hashjoin = true;
+bool enable_foreignjoin = true;
typedef struct
{
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 1d7b9fa..cf45c55 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -17,6 +17,7 @@
#include
#include "executor/executor.h"
+#include "foreign/fdwapi.h"
#include "optimizer/cost.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
@@ -264,17 +265,33 @@ add_paths_to_joinrel(PlannerInfo *root,
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).
+ * 5. Consider paths added by custom-scan providers, in addition to
+ * built-in paths.
*/
if (set_join_pathlist_hook)
set_join_pathlist_hook(root, joinrel, outerrel, innerrel,
restrictlist, jointype,
sjinfo, &semifactors,
param_source_rels, extra_lateral_rels);
+
+ /*
+ * 6. Consider paths added by FDWs when both outer and inner relations are
+ * managed by same foreign-data wrapper. Matching of foreign server and/or
+ * checkAsUser should be checked in GetForeignJoinPath by the FDW.
+ */
+ if (enable_foreignjoin &&
+ joinrel->fdwroutine && joinrel->fdwroutine->GetForeignJoinPath)
+ {
+ joinrel->fdwroutine->GetForeignJoinPath(root,
+ joinrel,
+ outerrel,
+ innerrel,
+ jointype,
+ sjinfo,
+ &semifactors,
+ restrictlist,
+ extra_lateral_rels);
+ }
}
/*
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 33720e8..9014a72 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -3236,6 +3236,9 @@ set_plan_disabling_options(const char *arg, GucContext context, GucSource source
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 d84dba7..7c2343f 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -875,6 +875,15 @@ static struct config_bool ConfigureNamesBool[] =
NULL, NULL, NULL
},
{
+ {"enable_foreignjoin", PGC_USERSET, QUERY_TUNING_METHOD,
+ gettext_noop("Enables the planner's use of foreign scan plans for foreign joins."),
+ 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 f8f9ce1..4fc4455 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -272,6 +272,7 @@
# - Planner Method Configuration -
#enable_bitmapscan = on
+#enable_foreignjoin = on
#enable_hashagg = on
#enable_hashjoin = on
#enable_indexscan = on
diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
index b494ff2..d4ab71a 100644
--- a/src/include/foreign/fdwapi.h
+++ b/src/include/foreign/fdwapi.h
@@ -82,6 +82,16 @@ 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 void (*ExplainForeignScan_function) (ForeignScanState *node,
struct ExplainState *es);
@@ -150,6 +160,10 @@ typedef struct FdwRoutine
/* Support functions for IMPORT FOREIGN SCHEMA */
ImportForeignSchema_function ImportForeignSchema;
+
+ /* Support functions for join push-down */
+ GetForeignJoinPath_function GetForeignJoinPath;
+
} FdwRoutine;
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index 9c2000b..e41c88a 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -61,6 +61,7 @@ extern bool enable_nestloop;
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);
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index 7991e99..4e02062 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -2,6 +2,7 @@ SELECT name, setting FROM pg_settings WHERE name LIKE 'enable%';
name | setting
----------------------+---------
enable_bitmapscan | on
+ enable_foreignjoin | on
enable_hashagg | on
enable_hashjoin | on
enable_indexonlyscan | on
@@ -12,7 +13,7 @@ SELECT name, setting FROM pg_settings WHERE name LIKE 'enable%';
enable_seqscan | on
enable_sort | on
enable_tidscan | on
-(11 rows)
+(12 rows)
CREATE TABLE foo2(fooid int, f2 int);
INSERT INTO foo2 VALUES(1, 11);