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) (),