pgsql_fdw_pushdown_v1.patch

text/plain

Filename: pgsql_fdw_pushdown_v1.patch
Type: text/plain
Part: 2
Message: Re: pgsql_fdw, FDW for PostgreSQL server

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: unified
Series: patch v1
File+
contrib/pgsql_fdw/deparse.c 0 0
contrib/pgsql_fdw/expected/pgsql_fdw.out 0 0
doc/src/sgml/pgsql-fdw.sgml 0 0
src/backend/nodes/nodeFuncs.c 0 0
src/include/nodes/nodeFuncs.h 0 0
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 ****
    </para>
  <synopsis>
  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)
  </synopsis>
   </sect2>
--- 225,235 ----
    </para>
  <synopsis>
  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)
  </synopsis>
   </sect2>
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) (),