diff --git a/contrib/pgsql_fdw/deparse.c b/contrib/pgsql_fdw/deparse.c
index cf9e775..594743f 100644
*** a/contrib/pgsql_fdw/deparse.c
--- b/contrib/pgsql_fdw/deparse.c
*************** typedef struct foreign_executable_cxt
*** 35,46 ****
RelOptInfo *foreignrel;
} foreign_executable_cxt;
/*
* Deparse query representation into SQL statement which suits for remote
! * PostgreSQL server.
*/
char *
! deparseSql(Oid relid, PlannerInfo *root, RelOptInfo *baserel)
{
StringInfoData foreign_relname;
StringInfoData sql; /* builder for SQL statement */
--- 35,53 ----
RelOptInfo *foreignrel;
} foreign_executable_cxt;
+ static bool is_foreign_expr(PlannerInfo *root, RelOptInfo *baserel, Expr *expr);
+ static bool foreign_expr_walker(Node *node, foreign_executable_cxt *context);
+ static bool is_builtin(Oid procid);
+ static bool contain_ext_param(Node *clause);
+ static bool contain_ext_param_walker(Node *node, void *context);
+
/*
* Deparse query representation into SQL statement which suits for remote
! * PostgreSQL server. Also some of quals in WHERE clause will be pushed down
! * If they are safe to be evaluated on the remote side.
*/
char *
! deparseSql(Oid relid, PlannerInfo *root, RelOptInfo *baserel, bool *has_param)
{
StringInfoData foreign_relname;
StringInfoData sql; /* builder for SQL statement */
*************** deparseSql(Oid relid, PlannerInfo *root,
*** 51,56 ****
--- 58,64 ----
const char *relname = NULL; /* plain relation name */
const char *q_nspname; /* quoted namespace name */
const char *q_relname; /* quoted relation name */
+ List *foreign_expr = NIL; /* list of Expr* evaluated on remote */
int i;
List *rtable = NIL;
List *context = NIL;
*************** deparseSql(Oid relid, PlannerInfo *root,
*** 59,73 ****
initStringInfo(&foreign_relname);
/*
! * First of all, determine which column should be retrieved for this scan.
*
* We do this before deparsing SELECT clause because attributes which are
* not used in neither reltargetlist nor baserel->baserestrictinfo, quals
* evaluated on local, can be replaced with literal "NULL" in the SELECT
* clause to reduce overhead of tuple handling tuple and data transfer.
*/
if (baserel->baserestrictinfo != NIL)
{
ListCell *lc;
foreach (lc, baserel->baserestrictinfo)
--- 67,91 ----
initStringInfo(&foreign_relname);
/*
! * First of all, determine which qual can be pushed down.
! *
! * The expressions which satisfy is_foreign_expr() are deparsed into WHERE
! * clause of result SQL string, and they could be removed from PlanState
! * to avoid duplicate evaluation at ExecScan().
! *
! * We never change the quals in the Plan node, because this execution might
! * be for a PREPAREd statement, thus the quals in the Plan node might be
! * reused to construct another PlanState for subsequent EXECUTE statement.
*
* We do this before deparsing SELECT clause because attributes which are
* not used in neither reltargetlist nor baserel->baserestrictinfo, quals
* evaluated on local, can be replaced with literal "NULL" in the SELECT
* clause to reduce overhead of tuple handling tuple and data transfer.
*/
+ *has_param = false;
if (baserel->baserestrictinfo != NIL)
{
+ List *local_qual = NIL;
ListCell *lc;
foreach (lc, baserel->baserestrictinfo)
*************** deparseSql(Oid relid, PlannerInfo *root,
*** 76,81 ****
--- 94,113 ----
List *attrs;
/*
+ * Determine whether the qual can be pushed down or not. If a qual
+ * can be pushed down and it contains external param, tell that to
+ * caller in order to fall back to fixed costs.
+ */
+ if (is_foreign_expr(root, baserel, ri->clause))
+ {
+ foreign_expr = lappend(foreign_expr, ri->clause);
+ if (contain_ext_param((Node*) ri->clause))
+ *has_param = true;
+ }
+ else
+ local_qual = lappend(local_qual, ri);
+
+ /*
* We need to know which attributes are used in qual evaluated
* on the local server, because they should be listed in the
* SELECT clause of remote query. We can ignore attributes
*************** deparseSql(Oid relid, PlannerInfo *root,
*** 87,92 ****
--- 119,130 ----
PVC_RECURSE_PLACEHOLDERS);
attr_used = list_union(attr_used, attrs);
}
+
+ /*
+ * Remove pushed down qualifiers from baserestrictinfo to avoid
+ * redundant evaluation.
+ */
+ baserel->baserestrictinfo = local_qual;
}
/*
*************** deparseSql(Oid relid, PlannerInfo *root,
*** 193,199 ****
--- 231,461 ----
*/
appendStringInfo(&sql, "FROM %s", foreign_relname.data);
+ /*
+ * deparse WHERE clause
+ */
+ if (foreign_expr != NIL)
+ {
+ Node *node;
+
+ node = (Node *) make_ands_explicit(foreign_expr);
+ appendStringInfo(&sql, " WHERE %s ",
+ deparse_expression(node, context, false, false));
+ list_free(foreign_expr);
+ foreign_expr = NIL;
+ }
+
elog(DEBUG3, "Remote SQL: %s", sql.data);
return sql.data;
}
+ /*
+ * Returns true if expr is safe to be evaluated on the foreign server.
+ */
+ static bool
+ is_foreign_expr(PlannerInfo *root, RelOptInfo *baserel, Expr *expr)
+ {
+ foreign_executable_cxt context;
+ context.root = root;
+ context.foreignrel = baserel;
+
+ /*
+ * An expression which includes any mutable function can't be pushed down
+ * because it's result is not stable. For example, pushing now() down to
+ * remote side would cause confusion from the clock offset.
+ * If we have routine mapping infrastructure in future release, we will be
+ * able to choose function to be pushed down in finer granularity.
+ */
+ if (contain_mutable_functions((Node *) expr))
+ return false;
+
+ /*
+ * Check that the expression consists of nodes which are known as safe to
+ * be pushed down.
+ */
+ if (foreign_expr_walker((Node *) expr, &context))
+ return false;
+
+ return true;
+ }
+
+ /*
+ * Return true if node includes any node which is not known as safe to be
+ * pushed down.
+ */
+ static bool
+ foreign_expr_walker(Node *node, foreign_executable_cxt *context)
+ {
+ Oid func;
+
+ if (node == NULL)
+ return false;
+
+ /*
+ * If given expression has valid collation, it can't be pushed down because
+ * it might has incompatible semantics on remote side.
+ */
+ if (exprCollation(node) != InvalidOid ||
+ exprInputCollation(node) != InvalidOid)
+ return true;
+
+ /*
+ * If return type of given expression is not built-in, it can't be pushed
+ * down because it might has incompatible semantics on remote side.
+ */
+ if (!is_builtin(exprType(node)))
+ return true;
+
+ /*
+ * If function used by the expression is not built-in, it can't be pushed
+ * down because it might has incompatible semantics on remote side.
+ */
+ func = exprFunction(node);
+ if (func != InvalidOid && !is_builtin(func))
+ return true;
+
+ switch (nodeTag(node))
+ {
+ case T_Const:
+ case T_BoolExpr:
+ case T_NullTest:
+ case T_DistinctExpr:
+ case T_RelabelType:
+ case T_FuncExpr:
+ /*
+ * These type of nodes are known as safe to be pushed down.
+ * Of course the subtree of the node, if any, should be checked
+ * continuously at the tail of this function.
+ */
+ break;
+ case T_Param:
+ /*
+ * Only external parameters can be pushed down.:
+ */
+ {
+ if (((Param *) node)->paramkind != PARAM_EXTERN)
+ return true;
+ }
+ break;
+ case T_ScalarArrayOpExpr:
+ /*
+ * Only built-in operators can be pushed down. In addition,
+ * underlying function must be built-in and immutable, but we don't
+ * check volatility here; such check must be done already with
+ * contain_mutable_functions.
+ */
+ {
+ ScalarArrayOpExpr *oe = (ScalarArrayOpExpr *) node;
+
+ if (!is_builtin(oe->opno))
+ return true;
+
+ /* operands are checked later */
+ }
+ break;
+ case T_OpExpr:
+ /*
+ * Only built-in operators can be pushed down. In addition,
+ * underlying function must be built-in and immutable, but we don't
+ * check volatility here; such check must be done already with
+ * contain_mutable_functions.
+ */
+ {
+ OpExpr *oe = (OpExpr *) node;
+
+ if (!is_builtin(oe->opno))
+ return true;
+
+ /* operands are checked later */
+ }
+ break;
+ case T_Var:
+ /*
+ * Var can be pushed down if it is in the foreign table.
+ * XXX Var of other relation can be here?
+ */
+ {
+ Var *var = (Var *) node;
+ foreign_executable_cxt *f_context;
+
+ f_context = (foreign_executable_cxt *) context;
+ if (var->varno != f_context->foreignrel->relid ||
+ var->varlevelsup != 0)
+ return true;
+ }
+ break;
+ case T_ArrayRef:
+ /*
+ * ArrayRef which holds non-built-in typed elements can't be pushed
+ * down.
+ */
+ {
+ if (!is_builtin(((ArrayRef *) node)->refelemtype))
+ return true;
+ }
+ break;
+ case T_ArrayExpr:
+ /*
+ * ArrayExpr which holds non-built-in typed elements can't be pushed
+ * down.
+ */
+ {
+ if (!is_builtin(((ArrayExpr *) node)->element_typeid))
+ return true;
+ }
+ break;
+ default:
+ {
+ ereport(DEBUG3,
+ (errmsg("expression is too complex"),
+ errdetail("%s", nodeToString(node))));
+ return true;
+ }
+ break;
+ }
+
+ return expression_tree_walker(node, foreign_expr_walker, context);
+ }
+
+ /*
+ * Return true if given object is one of built-in objects.
+ */
+ static bool
+ is_builtin(Oid oid)
+ {
+ return (oid < FirstNormalObjectId);
+ }
+
+ /*
+ * contain_ext_param
+ * Recursively search for Param nodes within a clause.
+ *
+ * Returns true if any parameter reference node with relkind PARAM_EXTERN
+ * found.
+ *
+ * This does not descend into subqueries, and so should be used only after
+ * reduction of sublinks to subplans, or in contexts where it's known there
+ * are no subqueries. There mustn't be outer-aggregate references either.
+ *
+ * XXX: These functions could be in core, src/backend/optimizer/util/clauses.c.
+ */
+ static bool
+ contain_ext_param(Node *clause)
+ {
+ return contain_ext_param_walker(clause, NULL);
+ }
+
+ static bool
+ contain_ext_param_walker(Node *node, void *context)
+ {
+ if (node == NULL)
+ return false;
+ if (IsA(node, Param))
+ {
+ Param *param = (Param *) node;
+
+ if (param->paramkind == PARAM_EXTERN)
+ return true; /* abort the tree traversal and return true */
+ }
+ return expression_tree_walker(node, contain_ext_param_walker, context);
+ }
diff --git a/contrib/pgsql_fdw/expected/pgsql_fdw.out b/contrib/pgsql_fdw/expected/pgsql_fdw.out
index 7aab32c..afd9e19 100644
*** a/contrib/pgsql_fdw/expected/pgsql_fdw.out
--- b/contrib/pgsql_fdw/expected/pgsql_fdw.out
*************** SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.
*** 218,229 ****
-- with WHERE clause
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 = '1';
! QUERY PLAN
! ------------------------------------------------------------------------------------------------------------------
Foreign Scan on public.ft1 t1
Output: c1, c2, c3, c4, c5, c6, c7
! Filter: ((t1.c1 = 101) AND ((t1.c6)::text = '1'::text) AND (t1.c7 = '1'::bpchar))
! Remote SQL: DECLARE pgsql_fdw_cursor_4 SCROLL CURSOR FOR SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1"
(4 rows)
SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 = '1';
--- 218,229 ----
-- with WHERE clause
EXPLAIN (VERBOSE, COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 = '1';
! QUERY PLAN
! ---------------------------------------------------------------------------------------------------------------------------------------
Foreign Scan on public.ft1 t1
Output: c1, c2, c3, c4, c5, c6, c7
! Filter: (((t1.c6)::text = '1'::text) AND (t1.c7 = '1'::bpchar))
! Remote SQL: DECLARE pgsql_fdw_cursor_4 SCROLL CURSOR FOR SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE ("C 1" = 101)
(4 rows)
SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 = '1';
*************** EXPLAIN (COSTS false) SELECT * FROM ft1
*** 331,350 ****
(3 rows)
EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = abs(t1.c2);
! QUERY PLAN
! -------------------------------------------------------------------------------------------------------------------
Foreign Scan on ft1 t1
! Filter: (c1 = abs(c2))
! Remote SQL: DECLARE pgsql_fdw_cursor_20 SCROLL CURSOR FOR SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1"
! (3 rows)
EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = t1.c2;
! QUERY PLAN
! -------------------------------------------------------------------------------------------------------------------
Foreign Scan on ft1 t1
! Filter: (c1 = c2)
! Remote SQL: DECLARE pgsql_fdw_cursor_21 SCROLL CURSOR FOR SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1"
! (3 rows)
DROP OPERATOR === (int, int) CASCADE;
DROP FUNCTION pgsql_fdw_abs(int);
--- 331,348 ----
(3 rows)
EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = abs(t1.c2);
! QUERY PLAN
! --------------------------------------------------------------------------------------------------------------------------------------------
Foreign Scan on ft1 t1
! Remote SQL: DECLARE pgsql_fdw_cursor_20 SCROLL CURSOR FOR SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE ("C 1" = abs(c2))
! (2 rows)
EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = t1.c2;
! QUERY PLAN
! ---------------------------------------------------------------------------------------------------------------------------------------
Foreign Scan on ft1 t1
! Remote SQL: DECLARE pgsql_fdw_cursor_21 SCROLL CURSOR FOR SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE ("C 1" = c2)
! (2 rows)
DROP OPERATOR === (int, int) CASCADE;
DROP FUNCTION pgsql_fdw_abs(int);
*************** DROP FUNCTION pgsql_fdw_abs(int);
*** 354,370 ****
-- 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 (COSTS false) EXECUTE st1(1, 2);
! QUERY PLAN
! -----------------------------------------------------------------------------------------------------------------------------------------
Nested Loop
-> Foreign Scan on ft1 t1
! Filter: (c1 = 1)
! Remote SQL: DECLARE pgsql_fdw_cursor_22 SCROLL CURSOR FOR SELECT "C 1", NULL, c3, NULL, NULL, NULL, NULL FROM "S 1"."T 1"
! -> Materialize
! -> Foreign Scan on ft2 t2
! Filter: (c1 = 2)
! Remote SQL: DECLARE pgsql_fdw_cursor_23 SCROLL CURSOR FOR SELECT "C 1", NULL, c3, NULL, NULL, NULL, NULL FROM "S 1"."T 1"
! (8 rows)
EXECUTE st1(1, 1);
c3 | c3
--- 352,365 ----
-- 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 (COSTS false) EXECUTE st1(1, 2);
! QUERY PLAN
! ------------------------------------------------------------------------------------------------------------------------------------------------------
Nested Loop
-> Foreign Scan on ft1 t1
! Remote SQL: DECLARE pgsql_fdw_cursor_22 SCROLL CURSOR FOR SELECT "C 1", NULL, c3, NULL, NULL, NULL, NULL FROM "S 1"."T 1" WHERE ("C 1" = 1)
! -> Foreign Scan on ft2 t2
! Remote SQL: DECLARE pgsql_fdw_cursor_23 SCROLL CURSOR FOR SELECT "C 1", NULL, c3, NULL, NULL, NULL, NULL FROM "S 1"."T 1" WHERE ("C 1" = 2)
! (5 rows)
EXECUTE st1(1, 1);
c3 | c3
*************** EXECUTE st1(101, 101);
*** 381,401 ****
-- subquery using stable function (can't be pushed down)
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 EXTRACT(dow FROM c4) = 6) ORDER BY c1;
EXPLAIN (COSTS false) EXECUTE st2(10, 20);
! QUERY PLAN
! ---------------------------------------------------------------------------------------------------------------------------------------------------
Sort
Sort Key: t1.c1
-> Hash Join
Hash Cond: (t1.c3 = t2.c3)
-> Foreign Scan on ft1 t1
! Filter: (c1 < 20)
! Remote SQL: DECLARE pgsql_fdw_cursor_28 SCROLL CURSOR FOR SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1"
-> Hash
-> HashAggregate
-> Foreign Scan on ft2 t2
! Filter: ((c1 > 10) AND (date_part('dow'::text, c4) = 6::double precision))
! Remote SQL: DECLARE pgsql_fdw_cursor_29 SCROLL CURSOR FOR SELECT "C 1", NULL, c3, c4, NULL, NULL, NULL FROM "S 1"."T 1"
! (12 rows)
EXECUTE st2(10, 20);
c1 | c2 | c3 | c4 | c5 | c6 | c7
--- 376,395 ----
-- subquery using stable function (can't be pushed down)
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 EXTRACT(dow FROM c4) = 6) ORDER BY c1;
EXPLAIN (COSTS false) EXECUTE st2(10, 20);
! QUERY PLAN
! -----------------------------------------------------------------------------------------------------------------------------------------------------------------------
Sort
Sort Key: t1.c1
-> Hash Join
Hash Cond: (t1.c3 = t2.c3)
-> Foreign Scan on ft1 t1
! Remote SQL: DECLARE pgsql_fdw_cursor_28 SCROLL CURSOR FOR SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE ("C 1" < 20)
-> Hash
-> HashAggregate
-> Foreign Scan on ft2 t2
! Filter: (date_part('dow'::text, c4) = 6::double precision)
! Remote SQL: DECLARE pgsql_fdw_cursor_29 SCROLL CURSOR FOR SELECT "C 1", NULL, c3, c4, NULL, NULL, NULL FROM "S 1"."T 1" WHERE ("C 1" > 10)
! (11 rows)
EXECUTE st2(10, 20);
c1 | c2 | c3 | c4 | c5 | c6 | c7
*************** EXECUTE st1(101, 101);
*** 412,432 ****
-- subquery using immutable function (can be pushed down)
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 EXTRACT(dow FROM c5) = 6) ORDER BY c1;
EXPLAIN (COSTS false) EXECUTE st3(10, 20);
! QUERY PLAN
! ---------------------------------------------------------------------------------------------------------------------------------------------------
Sort
Sort Key: t1.c1
-> Hash Join
Hash Cond: (t1.c3 = t2.c3)
-> Foreign Scan on ft1 t1
! Filter: (c1 < 20)
! Remote SQL: DECLARE pgsql_fdw_cursor_34 SCROLL CURSOR FOR SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1"
-> Hash
-> HashAggregate
-> Foreign Scan on ft2 t2
! Filter: ((c1 > 10) AND (date_part('dow'::text, c5) = 6::double precision))
! Remote SQL: DECLARE pgsql_fdw_cursor_35 SCROLL CURSOR FOR SELECT "C 1", NULL, c3, NULL, c5, NULL, NULL FROM "S 1"."T 1"
! (12 rows)
EXECUTE st3(10, 20);
c1 | c2 | c3 | c4 | c5 | c6 | c7
--- 406,425 ----
-- subquery using immutable function (can be pushed down)
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 EXTRACT(dow FROM c5) = 6) ORDER BY c1;
EXPLAIN (COSTS false) EXECUTE st3(10, 20);
! QUERY PLAN
! -----------------------------------------------------------------------------------------------------------------------------------------------------------------------
Sort
Sort Key: t1.c1
-> Hash Join
Hash Cond: (t1.c3 = t2.c3)
-> Foreign Scan on ft1 t1
! Remote SQL: DECLARE pgsql_fdw_cursor_34 SCROLL CURSOR FOR SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE ("C 1" < 20)
-> Hash
-> HashAggregate
-> Foreign Scan on ft2 t2
! Filter: (date_part('dow'::text, c5) = 6::double precision)
! Remote SQL: DECLARE pgsql_fdw_cursor_35 SCROLL CURSOR FOR SELECT "C 1", NULL, c3, NULL, c5, NULL, NULL FROM "S 1"."T 1" WHERE ("C 1" > 10)
! (11 rows)
EXECUTE st3(10, 20);
c1 | c2 | c3 | c4 | c5 | c6 | c7
*************** EXECUTE st3(20, 30);
*** 443,494 ****
-- custom plan should be chosen
PREPARE st4(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 = $1;
EXPLAIN (COSTS false) EXECUTE st4(1);
! QUERY PLAN
! -------------------------------------------------------------------------------------------------------------------
Foreign Scan on ft1 t1
! Filter: (c1 = 1)
! Remote SQL: DECLARE pgsql_fdw_cursor_40 SCROLL CURSOR FOR SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1"
! (3 rows)
EXPLAIN (COSTS false) EXECUTE st4(1);
! QUERY PLAN
! -------------------------------------------------------------------------------------------------------------------
Foreign Scan on ft1 t1
! Filter: (c1 = 1)
! Remote SQL: DECLARE pgsql_fdw_cursor_41 SCROLL CURSOR FOR SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1"
! (3 rows)
EXPLAIN (COSTS false) EXECUTE st4(1);
! QUERY PLAN
! -------------------------------------------------------------------------------------------------------------------
Foreign Scan on ft1 t1
! Filter: (c1 = 1)
! Remote SQL: DECLARE pgsql_fdw_cursor_42 SCROLL CURSOR FOR SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1"
! (3 rows)
EXPLAIN (COSTS false) EXECUTE st4(1);
! QUERY PLAN
! -------------------------------------------------------------------------------------------------------------------
Foreign Scan on ft1 t1
! Filter: (c1 = 1)
! Remote SQL: DECLARE pgsql_fdw_cursor_43 SCROLL CURSOR FOR SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1"
! (3 rows)
EXPLAIN (COSTS false) EXECUTE st4(1);
! QUERY PLAN
! -------------------------------------------------------------------------------------------------------------------
Foreign Scan on ft1 t1
! Filter: (c1 = 1)
! Remote SQL: DECLARE pgsql_fdw_cursor_44 SCROLL CURSOR FOR SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1"
! (3 rows)
EXPLAIN (COSTS false) EXECUTE st4(1);
! QUERY PLAN
! -------------------------------------------------------------------------------------------------------------------
Foreign Scan on ft1 t1
! Filter: (c1 = 1)
! Remote SQL: DECLARE pgsql_fdw_cursor_46 SCROLL CURSOR FOR SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1"
! (3 rows)
-- cleanup
DEALLOCATE st1;
--- 436,481 ----
-- custom plan should be chosen
PREPARE st4(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 = $1;
EXPLAIN (COSTS false) EXECUTE st4(1);
! QUERY PLAN
! --------------------------------------------------------------------------------------------------------------------------------------
Foreign Scan on ft1 t1
! Remote SQL: DECLARE pgsql_fdw_cursor_40 SCROLL CURSOR FOR SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE ("C 1" = 1)
! (2 rows)
EXPLAIN (COSTS false) EXECUTE st4(1);
! QUERY PLAN
! --------------------------------------------------------------------------------------------------------------------------------------
Foreign Scan on ft1 t1
! Remote SQL: DECLARE pgsql_fdw_cursor_41 SCROLL CURSOR FOR SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE ("C 1" = 1)
! (2 rows)
EXPLAIN (COSTS false) EXECUTE st4(1);
! QUERY PLAN
! --------------------------------------------------------------------------------------------------------------------------------------
Foreign Scan on ft1 t1
! Remote SQL: DECLARE pgsql_fdw_cursor_42 SCROLL CURSOR FOR SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE ("C 1" = 1)
! (2 rows)
EXPLAIN (COSTS false) EXECUTE st4(1);
! QUERY PLAN
! --------------------------------------------------------------------------------------------------------------------------------------
Foreign Scan on ft1 t1
! Remote SQL: DECLARE pgsql_fdw_cursor_43 SCROLL CURSOR FOR SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE ("C 1" = 1)
! (2 rows)
EXPLAIN (COSTS false) EXECUTE st4(1);
! QUERY PLAN
! --------------------------------------------------------------------------------------------------------------------------------------
Foreign Scan on ft1 t1
! Remote SQL: DECLARE pgsql_fdw_cursor_44 SCROLL CURSOR FOR SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE ("C 1" = 1)
! (2 rows)
EXPLAIN (COSTS false) EXECUTE st4(1);
! QUERY PLAN
! --------------------------------------------------------------------------------------------------------------------------------------
Foreign Scan on ft1 t1
! Remote SQL: DECLARE pgsql_fdw_cursor_46 SCROLL CURSOR FOR SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE ("C 1" = 1)
! (2 rows)
-- cleanup
DEALLOCATE st1;
diff --git a/contrib/pgsql_fdw/pgsql_fdw.c b/contrib/pgsql_fdw/pgsql_fdw.c
index a649669..86abd0a 100644
*** a/contrib/pgsql_fdw/pgsql_fdw.c
--- b/contrib/pgsql_fdw/pgsql_fdw.c
*************** static void estimate_costs(PlannerInfo *
*** 123,130 ****
static void execute_query(ForeignScanState *node);
static PGresult *fetch_result(ForeignScanState *node);
static void store_result(ForeignScanState *node, PGresult *res);
- static bool contain_ext_param(Node *clause);
- static bool contain_ext_param_walker(Node *node, void *context);
/* Exported functions, but not written in pgsql_fdw.h. */
void _PG_init(void);
--- 123,128 ----
*************** pgsqlPlanForeignScan(Oid foreigntableid,
*** 188,201 ****
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
--- 186,216 ----
List *fdw_private = NIL;
ForeignTable *table;
ForeignServer *server;
+ bool has_param;
/* Construct FdwPlan with cost estimates */
fdwplan = makeNode(FdwPlan);
! sql = deparseSql(foreigntableid, root, baserel, &has_param);
table = GetForeignTable(foreigntableid);
server = GetForeignServer(table->serverid);
!
! /*
! * If the baserestrictinfo contains any Param node with paramkind
! * PARAM_EXTERNAL as part of push-down-able condition, we need to
! * groundless fixed costs because we can't get actual parameter values
! * here, so we use fixed and large costs as second best so that planner
! * tend to choose custom plan.
! *
! * See comments in plancache.c for details of custom plan.
! */
! if (has_param)
! {
! fdwplan->startup_cost = CONNECTION_COSTS;
! fdwplan->total_cost = 10000; /* groundless large costs */
! }
! else
! 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
*************** estimate_costs(PlannerInfo *root, RelOpt
*** 541,547 ****
const char *sql, Oid serverid,
Cost *startup_cost, Cost *total_cost)
{
- ListCell *lc;
ForeignServer *server;
UserMapping *user;
PGconn *conn = NULL;
--- 556,561 ----
*************** estimate_costs(PlannerInfo *root, RelOpt
*** 552,578 ****
int n;
/*
- * If the baserestrictinfo contains any Param node with paramkind
- * PARAM_EXTERNAL, we need result of EXPLAIN for EXECUTE statement, not for
- * simple SELECT statement. However, we can't get actual parameter values
- * here, so we use fixed and large costs as second best so that planner
- * tend to choose custom plan.
- *
- * See comments in plancache.c for details of custom plan.
- */
- foreach(lc, baserel->baserestrictinfo)
- {
- RestrictInfo *rs = (RestrictInfo *) lfirst(lc);
- if (contain_ext_param((Node *) rs->clause))
- {
- *startup_cost = CONNECTION_COSTS;
- *total_cost = 10000; /* groundless large costs */
-
- return;
- }
- }
-
- /*
* Get connection to the foreign server. Connection manager would
* establish new connection if necessary.
*/
--- 566,571 ----
*************** store_result(ForeignScanState *node, PGr
*** 843,879 ****
tuplestore_donestoring(festate->tuples);
}
-
- /*
- * contain_ext_param
- * Recursively search for Param nodes within a clause.
- *
- * Returns true if any parameter reference node with relkind PARAM_EXTERN
- * found.
- *
- * This does not descend into subqueries, and so should be used only after
- * reduction of sublinks to subplans, or in contexts where it's known there
- * are no subqueries. There mustn't be outer-aggregate references either.
- *
- * XXX: These functions could be in core, src/backend/optimizer/util/clauses.c.
- */
- static bool
- contain_ext_param(Node *clause)
- {
- return contain_ext_param_walker(clause, NULL);
- }
-
- static bool
- contain_ext_param_walker(Node *node, void *context)
- {
- if (node == NULL)
- return false;
- if (IsA(node, Param))
- {
- Param *param = (Param *) node;
-
- if (param->paramkind == PARAM_EXTERN)
- return true; /* abort the tree traversal and return true */
- }
- return expression_tree_walker(node, contain_ext_param_walker, context);
- }
--- 836,838 ----
diff --git a/contrib/pgsql_fdw/pgsql_fdw.h b/contrib/pgsql_fdw/pgsql_fdw.h
index f6394ce..690d58b 100644
*** a/contrib/pgsql_fdw/pgsql_fdw.h
--- b/contrib/pgsql_fdw/pgsql_fdw.h
*************** int ExtractConnectionOptions(List *defel
*** 23,28 ****
const char **values);
/* in deparse.c */
! char *deparseSql(Oid relid, PlannerInfo *root, RelOptInfo *baserel);
#endif /* PGSQL_FDW_H */
--- 23,31 ----
const char **values);
/* in deparse.c */
! char *deparseSql(Oid relid,
! PlannerInfo *root,
! RelOptInfo *baserel,
! bool *has_param);
#endif /* PGSQL_FDW_H */
diff --git a/doc/src/sgml/pgsql-fdw.sgml b/doc/src/sgml/pgsql-fdw.sgml
index c9d5d8f..df2e7f6 100644
*** a/doc/src/sgml/pgsql-fdw.sgml
--- b/doc/src/sgml/pgsql-fdw.sgml
*************** postgres=# SELECT pgsql_fdw_disconnect(s
*** 234,245 ****
postgres=# EXPLAIN SELECT aid FROM pgbench_accounts WHERE abalance < 0;
! QUERY PLAN
! --------------------------------------------------------------------------------------------------------------------------
! Foreign Scan on pgbench_accounts (cost=100.00..8105.13 rows=302613 width=8)
! Filter: (abalance < 0)
! Remote SQL: DECLARE pgsql_fdw_cursor_3 SCROLL CURSOR FOR SELECT aid, NULL, abalance, NULL FROM public.pgbench_accounts
! (3 rows)
--- 234,244 ----
postgres=# EXPLAIN SELECT aid FROM pgbench_accounts WHERE abalance < 0;
! QUERY PLAN
! ------------------------------------------------------------------------------------------------------------------------------------------------
! Foreign Scan on pgbench_accounts (cost=100.00..8861.66 rows=518 width=8)
! Remote SQL: DECLARE pgsql_fdw_cursor_1 SCROLL CURSOR FOR SELECT aid, NULL, abalance, NULL FROM public.pgbench_accounts WHERE (abalance < 0)
! (2 rows)
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 51459c4..c017d6b 100644
*** a/src/backend/nodes/nodeFuncs.c
--- b/src/backend/nodes/nodeFuncs.c
*************** leftmostLoc(int loc1, int loc2)
*** 1405,1410 ****
--- 1405,1449 ----
return Min(loc1, loc2);
}
+ /*
+ * exprFunction -
+ * returns the Oid of the function used by the expression.
+ *
+ * Result is InvalidOid if the node type doesn't store this information.
+ */
+ Oid
+ exprFunction(const Node *expr)
+ {
+ Oid func;
+
+ if (!expr)
+ return InvalidOid;
+
+ switch (nodeTag(expr))
+ {
+ case T_Aggref:
+ func = ((const Aggref *) expr)->aggfnoid;
+ break;
+ case T_WindowFunc:
+ func = ((const WindowFunc *) expr)->winfnoid;
+ break;
+ case T_FuncExpr:
+ func = ((const FuncExpr *) expr)->funcid;
+ break;
+ case T_OpExpr:
+ func = ((const OpExpr *) expr)->opfuncid;
+ break;
+ case T_ScalarArrayOpExpr:
+ func = ((const ScalarArrayOpExpr *) expr)->opfuncid;
+ break;
+ case T_ArrayCoerceExpr:
+ func = ((const ArrayCoerceExpr *) expr)->elemfuncid;
+ break;
+ default:
+ func = InvalidOid;
+ }
+ return func;
+ }
/*
* Standard expression-tree walking support
diff --git a/src/include/nodes/nodeFuncs.h b/src/include/nodes/nodeFuncs.h
index def4f31..a3ebe5c 100644
*** a/src/include/nodes/nodeFuncs.h
--- b/src/include/nodes/nodeFuncs.h
*************** extern void exprSetInputCollation(Node *
*** 38,43 ****
--- 38,45 ----
extern int exprLocation(const Node *expr);
+ extern Oid exprFunction(const Node *expr);
+
extern bool expression_tree_walker(Node *node, bool (*walker) (),
void *context);
extern Node *expression_tree_mutator(Node *node, Node *(*mutator) (),