diff --git a/contrib/pgsql_fdw/deparse.c b/contrib/pgsql_fdw/deparse.c index def5cb3..26b410d 100644 *** a/contrib/pgsql_fdw/deparse.c --- b/contrib/pgsql_fdw/deparse.c *************** typedef struct foreign_executable_cxt *** 35,43 **** 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) --- 35,48 ---- 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); + /* * 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) *************** deparseSql(Oid relid, PlannerInfo *root, *** 51,56 **** --- 56,62 ---- 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,65 **** 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 --- 65,79 ---- 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 *************** deparseSql(Oid relid, PlannerInfo *root, *** 75,80 **** --- 89,98 ---- RestrictInfo *ri = (RestrictInfo *) lfirst(lc); List *attrs; + /* Determine whether the qual can be pushed down or not. */ + if (is_foreign_expr(root, baserel, ri->clause)) + foreign_expr = lappend(foreign_expr, ri->clause); + /* * We need to know which attributes are used in qual evaluated * on the local server, because they should be listed in the *************** deparseSql(Oid relid, PlannerInfo *root, *** 193,199 **** --- 211,408 ---- */ 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 bacause + * it might has uncompatible 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 bacause it might has uncompatible 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 bacause it might has uncompatible 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); + } + diff --git a/contrib/pgsql_fdw/expected/pgsql_fdw.out b/contrib/pgsql_fdw/expected/pgsql_fdw.out index dfac57a..2166fb4 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.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" 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,349 **** (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; --- 331,349 ---- (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" WHERE ("C 1" = abs(c2)) (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" WHERE ("C 1" = c2) (3 rows) DROP OPERATOR === (int, int) CASCADE; *************** 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 --- 354,369 ---- -- 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" WHERE ("C 1" = 1) ! -> 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" WHERE ("C 1" = 2) ! (7 rows) EXECUTE st1(1, 1); c3 | c3 *************** EXECUTE st1(101, 101); *** 381,388 **** -- 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 -> Nested Loop --- 380,387 ---- -- 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 -> Nested Loop *************** EXPLAIN (COSTS false) EXECUTE st2(10, 20 *** 390,399 **** -> 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" -> 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" (11 rows) EXECUTE st2(10, 20); --- 389,398 ---- -> 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" WHERE ("C 1" > 10) -> 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" WHERE ("C 1" < 20) (11 rows) EXECUTE st2(10, 20); *************** EXECUTE st1(101, 101); *** 411,418 **** -- 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 -> Nested Loop --- 410,417 ---- -- 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 -> Nested Loop *************** EXPLAIN (COSTS false) EXECUTE st3(10, 20 *** 420,429 **** -> 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" -> 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" (11 rows) EXECUTE st3(10, 20); --- 419,428 ---- -> 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" WHERE ("C 1" > 10) -> 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" WHERE ("C 1" < 20) (11 rows) EXECUTE st3(10, 20); *************** EXECUTE st3(20, 30); *** 441,491 **** -- 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 --- 440,490 ---- -- 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" WHERE ("C 1" = 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" WHERE ("C 1" = 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" WHERE ("C 1" = 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" WHERE ("C 1" = 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" WHERE ("C 1" = 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" WHERE ("C 1" = 1) (3 rows) -- cleanup diff --git a/doc/src/sgml/pgsql-fdw.sgml b/doc/src/sgml/pgsql-fdw.sgml index 299f4b6..1715257 100644 *** a/doc/src/sgml/pgsql-fdw.sgml --- b/doc/src/sgml/pgsql-fdw.sgml *************** postgres=# SELECT pgsql_fdw_disconnect(s *** 225,235 **** postgres=# EXPLAIN SELECT aid FROM pgbench_accounts WHERE abalance < 0; ! QUERY PLAN ! -------------------------------------------------------------------------------------------------------------------------- ! Foreign Scan on pgbench_accounts (cost=100.00..8046.37 rows=301037 width=8) Filter: (abalance < 0) ! Remote SQL: DECLARE pgsql_fdw_cursor_1 SCROLL CURSOR FOR SELECT aid, NULL, abalance, NULL FROM public.pgbench_accounts (3 rows) --- 225,235 ---- postgres=# EXPLAIN SELECT aid FROM pgbench_accounts WHERE abalance < 0; ! QUERY PLAN ! ------------------------------------------------------------------------------------------------------------------------------------------------- ! Foreign Scan on pgbench_accounts (cost=100.00..8798.96 rows=1 width=97) Filter: (abalance < 0) ! Remote SQL: DECLARE pgsql_fdw_cursor_2 SCROLL CURSOR FOR SELECT aid, bid, abalance, filler FROM public.pgbench_accounts WHERE (abalance < 0) (3 rows) diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index 48b0590..f2acd46 100644 *** a/src/backend/nodes/nodeFuncs.c --- b/src/backend/nodes/nodeFuncs.c *************** leftmostLoc(int loc1, int loc2) *** 1405,1410 **** --- 1405,1447 ---- return Min(loc1, loc2); } + /* + * exprFunction - + * returns the Oid of the function used by the expression's. + */ + 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 4980dd8..09d877f 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) (),