pgsql_fdw_pushdown_v13.patch

text/plain

Filename: pgsql_fdw_pushdown_v13.patch
Type: text/plain
Part: 1
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 v13
File+
contrib/pgsql_fdw/deparse.c 859 0
contrib/pgsql_fdw/expected/pgsql_fdw.out 126 67
contrib/pgsql_fdw/pgsql_fdw.c 54 17
contrib/pgsql_fdw/pgsql_fdw.h 9 0
contrib/pgsql_fdw/sql/pgsql_fdw.sql 14 0
diff --git a/contrib/pgsql_fdw/deparse.c b/contrib/pgsql_fdw/deparse.c
index e1bbf6b..148d0b4 100644
--- a/contrib/pgsql_fdw/deparse.c
+++ b/contrib/pgsql_fdw/deparse.c
@@ -14,6 +14,8 @@
 
 #include "access/transam.h"
 #include "catalog/pg_class.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_type.h"
 #include "commands/defrem.h"
 #include "foreign/foreign.h"
 #include "lib/stringinfo.h"
@@ -22,9 +24,11 @@
 #include "nodes/makefuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/var.h"
+#include "parser/parser.h"
 #include "parser/parsetree.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
+#include "utils/syscache.h"
 
 #include "pgsql_fdw.h"
 
@@ -35,15 +39,39 @@ typedef struct foreign_executable_cxt
 {
 	PlannerInfo	   *root;
 	RelOptInfo	   *foreignrel;
+	bool			has_param;
 } foreign_executable_cxt;
 
 /*
  * Get string representation which can be used in SQL statement from a node.
  */
+static void deparseExpr(StringInfo buf, Expr *expr, PlannerInfo *root);
 static void deparseRelation(StringInfo buf, Oid relid, PlannerInfo *root,
 							bool need_prefix);
 static void deparseVar(StringInfo buf, Var *node, PlannerInfo *root,
 					   bool need_prefix);
+static void deparseConst(StringInfo buf, Const *node, PlannerInfo *root);
+static void deparseBoolExpr(StringInfo buf, BoolExpr *node, PlannerInfo *root);
+static void deparseNullTest(StringInfo buf, NullTest *node, PlannerInfo *root);
+static void deparseDistinctExpr(StringInfo buf, DistinctExpr *node,
+								PlannerInfo *root);
+static void deparseRelabelType(StringInfo buf, RelabelType *node,
+							   PlannerInfo *root);
+static void deparseFuncExpr(StringInfo buf, FuncExpr *node, PlannerInfo *root);
+static void deparseParam(StringInfo buf, Param *node, PlannerInfo *root);
+static void deparseScalarArrayOpExpr(StringInfo buf, ScalarArrayOpExpr *node,
+									 PlannerInfo *root);
+static void deparseOpExpr(StringInfo buf, OpExpr *node, PlannerInfo *root);
+static void deparseArrayRef(StringInfo buf, ArrayRef *node, PlannerInfo *root);
+static void deparseArrayExpr(StringInfo buf, ArrayExpr *node, PlannerInfo *root);
+
+/*
+ * Determine whether an expression can be evaluated on remote side safely.
+ */
+static bool is_foreign_expr(PlannerInfo *root, RelOptInfo *baserel, Expr *expr,
+							bool *has_param);
+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
@@ -150,6 +178,107 @@ deparseSimpleSql(StringInfo buf,
 }
 
 /*
+ * Examine each element in the list baserestrictinfo of baserel, and sort them
+ * into three groups: remote_conds contains conditions which can be evaluated
+ *   - remote_conds is push-down safe, and don't contain any Param node
+ *   - param_conds is push-down safe, but contain some Param node
+ *   - local_conds is not push-down safe
+ *
+ * Only remote_conds can be used in remote EXPLAIN, and remote_conds and
+ * param_conds can be used in final remote query.
+ */
+void
+sortConditions(PlannerInfo *root,
+				   RelOptInfo *baserel,
+				   List **remote_conds,
+				   List **param_conds,
+				   List **local_conds)
+{
+	ListCell	   *lc;
+	bool			has_param;
+
+	Assert(remote_conds);
+	Assert(param_conds);
+	Assert(local_conds);
+
+	foreach(lc, baserel->baserestrictinfo)
+	{
+		RestrictInfo *ri = (RestrictInfo *) lfirst(lc);
+
+		if (is_foreign_expr(root, baserel, ri->clause, &has_param))
+		{
+			if (has_param)
+				*param_conds = lappend(*param_conds, ri);
+			else
+				*remote_conds = lappend(*remote_conds, ri);
+		}
+		else
+			*local_conds = lappend(*local_conds, ri);
+	}
+}
+
+/*
+ * Deparse given expression into buf.  Actual string operation is delegated to
+ * node-type-specific functions.
+ *
+ * Note that switch statement of this function MUST match the one in
+ * foreign_expr_walker to avoid unsupported error..
+ */
+static void
+deparseExpr(StringInfo buf, Expr *node, PlannerInfo *root)
+{
+	/*
+	 * This part must be match foreign_expr_walker.
+	 */
+	switch (nodeTag(node))
+	{
+		case T_Const:
+			deparseConst(buf, (Const *) node, root);
+			break;
+		case T_BoolExpr:
+			deparseBoolExpr(buf, (BoolExpr *) node, root);
+			break;
+		case T_NullTest:
+			deparseNullTest(buf, (NullTest *) node, root);
+			break;
+		case T_DistinctExpr:
+			deparseDistinctExpr(buf, (DistinctExpr *) node, root);
+			break;
+		case T_RelabelType:
+			deparseRelabelType(buf, (RelabelType *) node, root);
+			break;
+		case T_FuncExpr:
+			deparseFuncExpr(buf, (FuncExpr *) node, root);
+			break;
+		case T_Param:
+			deparseParam(buf, (Param *) node, root);
+			break;
+		case T_ScalarArrayOpExpr:
+			deparseScalarArrayOpExpr(buf, (ScalarArrayOpExpr *) node, root);
+			break;
+		case T_OpExpr:
+			deparseOpExpr(buf, (OpExpr *) node, root);
+			break;
+		case T_Var:
+			deparseVar(buf, (Var *) node, root, false);
+			break;
+		case T_ArrayRef:
+			deparseArrayRef(buf, (ArrayRef *) node, root);
+			break;
+		case T_ArrayExpr:
+			deparseArrayExpr(buf, (ArrayExpr *) node, root);
+			break;
+		default:
+			{
+				ereport(ERROR,
+						(errmsg("unsupported expression for deparse"),
+						 errdetail("%s", nodeToString(node))));
+			}
+			break;
+	}
+}
+
+/*
  * Deparse node into buf, with relation qualifier if need_prefix was true.  If
  * node is a column of a foreign table, use value of colname FDW option (if any)
  * instead of attribute name.
@@ -285,3 +414,733 @@ deparseRelation(StringInfo buf,
 		appendStringInfo(buf, "%s.", q_nspname);
 	appendStringInfo(buf, "%s", q_relname);
 }
+
+/*
+ * Deparse given constant value into buf.  This function have to be kept in
+ * sync with get_const_expr.
+ */
+static void
+deparseConst(StringInfo buf,
+			 Const *node,
+			 PlannerInfo *root)
+{
+	Oid			typoutput;
+	bool		typIsVarlena;
+	char	   *extval;
+	bool		isfloat = false;
+	bool		needlabel;
+
+	if (node->constisnull)
+	{
+		appendStringInfo(buf, "NULL");
+		return;
+	}
+
+	getTypeOutputInfo(node->consttype,
+					  &typoutput, &typIsVarlena);
+	extval = OidOutputFunctionCall(typoutput, node->constvalue);
+
+	switch (node->consttype)
+	{
+		case ANYARRAYOID:
+		case ANYNONARRAYOID:
+			elog(ERROR, "anyarray and anyenum are not supported");
+			break;
+		case INT2OID:
+		case INT4OID:
+		case INT8OID:
+		case OIDOID:
+		case FLOAT4OID:
+		case FLOAT8OID:
+		case NUMERICOID:
+			{
+				/*
+				 * No need to quote unless they contain special values such as
+				 * 'Nan'.
+				 */
+				if (strspn(extval, "0123456789+-eE.") == strlen(extval))
+				{
+					if (extval[0] == '+' || extval[0] == '-')
+						appendStringInfo(buf, "(%s)", extval);
+					else
+						appendStringInfoString(buf, extval);
+					if (strcspn(extval, "eE.") != strlen(extval))
+						isfloat = true;	/* it looks like a float */
+				}
+				else
+					appendStringInfo(buf, "'%s'", extval);
+			}
+			break;
+		case BITOID:
+		case VARBITOID:
+			appendStringInfo(buf, "B'%s'", extval);
+			break;
+		case BOOLOID:
+			if (strcmp(extval, "t") == 0)
+				appendStringInfoString(buf, "true");
+			else
+				appendStringInfoString(buf, "false");
+			break;
+
+		default:
+			{
+				const char *valptr;
+
+				appendStringInfoChar(buf, '\'');
+				for (valptr = extval; *valptr; valptr++)
+				{
+					char		ch = *valptr;
+
+					/*
+					 * standard_conforming_strings of remote session should be
+					 * set to similar value as local session.
+					 */
+					if (SQL_STR_DOUBLE(ch, !standard_conforming_strings))
+						appendStringInfoChar(buf, ch);
+					appendStringInfoChar(buf, ch);
+				}
+				appendStringInfoChar(buf, '\'');
+			}
+			break;
+	}
+
+	/*
+	 * Append ::typename unless the constant will be implicitly typed as the
+	 * right type when it is read in.
+	 *
+	 * XXX this code has to be kept in sync with the behavior of the parser,
+	 * especially make_const.
+	 */
+	switch (node->consttype)
+	{
+		case BOOLOID:
+		case INT4OID:
+		case UNKNOWNOID:
+			needlabel = false;
+			break;
+		case NUMERICOID:
+			needlabel = !isfloat || (node->consttypmod >= 0);
+			break;
+		default:
+			needlabel = true;
+			break;
+	}
+	if (needlabel)
+	{
+		appendStringInfo(buf, "::%s",
+						 format_type_with_typemod(node->consttype,
+												  node->consttypmod));
+	}
+}
+
+static void
+deparseBoolExpr(StringInfo buf,
+				BoolExpr *node,
+				PlannerInfo *root)
+{
+	ListCell   *lc;
+	char	   *op;
+	bool		first;
+
+	switch (node->boolop)
+	{
+		case AND_EXPR:
+			op = "AND";
+			break;
+		case OR_EXPR:
+			op = "OR";
+			break;
+		case NOT_EXPR:
+			appendStringInfo(buf, "(NOT ");
+			deparseExpr(buf, list_nth(node->args, 0), root);
+			appendStringInfo(buf, ")");
+			return;
+	}
+
+	first = true;
+	appendStringInfo(buf, "(");
+	foreach(lc, node->args)
+	{
+		if (!first)
+			appendStringInfo(buf, " %s ", op);
+		deparseExpr(buf, (Expr *) lfirst(lc), root);
+		first = false;
+	}
+	appendStringInfo(buf, ")");
+}
+
+/*
+ * Deparse given IS [NOT] NULL test expression into buf.
+ */
+static void
+deparseNullTest(StringInfo buf,
+				NullTest *node,
+				PlannerInfo *root)
+{
+	appendStringInfoChar(buf, '(');
+	deparseExpr(buf, node->arg, root);
+	if (node->nulltesttype == IS_NULL)
+		appendStringInfo(buf, " IS NULL)");
+	else
+		appendStringInfo(buf, " IS NOT NULL)");
+}
+
+static void
+deparseDistinctExpr(StringInfo buf,
+					DistinctExpr *node,
+					PlannerInfo *root)
+{
+	Assert(list_length(node->args) == 2);
+
+	deparseExpr(buf, linitial(node->args), root);
+	appendStringInfo(buf, " IS DISTINCT FROM ");
+	deparseExpr(buf, lsecond(node->args), root);
+}
+
+static void
+deparseRelabelType(StringInfo buf,
+				   RelabelType *node,
+				   PlannerInfo *root)
+{
+	char	   *typname;
+
+	Assert(node->arg);
+
+	/* We don't need to deparse cast when argument has same type as result. */
+	if (IsA(node->arg, Const) &&
+		((Const *) node->arg)->consttype == node->resulttype &&
+		((Const *) node->arg)->consttypmod == -1)
+	{
+		deparseExpr(buf, node->arg, root);
+		return;
+	}
+
+	typname = format_type_with_typemod(node->resulttype, node->resulttypmod);
+	appendStringInfoChar(buf, '(');
+	deparseExpr(buf, node->arg, root);
+	appendStringInfo(buf, ")::%s", typname);
+}
+
+/*
+ * Deparse given node which represents a function call into buf.  We treat only
+ * explicit function call and explicit cast (coerce), because others are
+ * processed on remote side if necessary.
+ *
+ * Function name (and type name) is always qualified by schema name to avoid
+ * problems caused by different setting of search_path on remote side.
+ */
+static void
+deparseFuncExpr(StringInfo buf,
+				FuncExpr *node,
+				PlannerInfo *root)
+{
+	Oid				pronamespace;
+	const char	   *schemaname;
+	const char	   *funcname;
+	ListCell	   *arg;
+	bool			first;
+
+	pronamespace = get_func_namespace(node->funcid);
+	schemaname = quote_identifier(get_namespace_name(pronamespace));
+	funcname = quote_identifier(get_func_name(node->funcid));
+
+	if (node->funcformat == COERCE_EXPLICIT_CALL)
+	{
+		/* Function call, deparse all arguments recursively. */
+		appendStringInfo(buf, "%s.%s(", schemaname, funcname);
+		first = true;
+		foreach(arg, node->args)
+		{
+			if (!first)
+				appendStringInfo(buf, ", ");
+			deparseExpr(buf, lfirst(arg), root);
+			first = false;
+		}
+		appendStringInfoChar(buf, ')');
+	}
+	else if (node->funcformat == COERCE_EXPLICIT_CAST)
+	{
+		/* Explicit cast, deparse only first argument. */
+		appendStringInfoChar(buf, '(');
+		deparseExpr(buf, linitial(node->args), root);
+		appendStringInfo(buf, ")::%s", funcname);
+	}
+	else
+	{
+		/* Implicit cast, deparse only first argument. */
+		deparseExpr(buf, linitial(node->args), root);
+	}
+}
+
+/*
+ * Deparse given Param node into buf.
+ *
+ * We don't renumber parameter id, because skipping $1 is not cause problem
+ * as far as we pass through all arguments.
+ */
+static void
+deparseParam(StringInfo buf,
+			 Param *node,
+			 PlannerInfo *root)
+{
+	Assert(node->paramkind == PARAM_EXTERN);
+
+	appendStringInfo(buf, "$%d", node->paramid);
+}
+
+/*
+ * Deparse given ScalarArrayOpExpr expression into buf.  To avoid problems
+ * around priority of operations, we always parenthesize the arguments.  Also we
+ * use OPERATOR(schema.operator) notation to determine remote operator exactly.
+ */
+static void
+deparseScalarArrayOpExpr(StringInfo buf,
+						 ScalarArrayOpExpr *node,
+						 PlannerInfo *root)
+{
+	HeapTuple	tuple;
+	Form_pg_operator form;
+	const char *opnspname;
+	char	   *opname;
+	Expr	   *arg1;
+	Expr	   *arg2;
+
+	/* Retrieve necessary information about the operator from system catalog. */
+	tuple = SearchSysCache1(OPEROID, ObjectIdGetDatum(node->opno));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for operator %u", node->opno);
+	form = (Form_pg_operator) GETSTRUCT(tuple);
+	/* opname is not a SQL identifier, so we don't need to quote it. */
+	opname = NameStr(form->oprname);
+	opnspname = quote_identifier(get_namespace_name(form->oprnamespace));
+	ReleaseSysCache(tuple);
+
+	/* Sanity check. */
+	Assert(list_length(node->args) == 2);
+
+	/* Always parenthesize the expression. */
+	appendStringInfoChar(buf, '(');
+
+	/* Extract operands. */
+	arg1 = linitial(node->args);
+	arg2 = lsecond(node->args);
+
+	/* Deparse fully qualified operator name. */
+	deparseExpr(buf, arg1, root);
+	appendStringInfo(buf, " OPERATOR(%s.%s) %s (",
+					 opnspname, opname, node->useOr ? "ANY" : "ALL");
+	deparseExpr(buf, arg2, root);
+	appendStringInfoChar(buf, ')');
+
+	/* Always parenthesize the expression. */
+	appendStringInfoChar(buf, ')');
+}
+
+/*
+ * Deparse given operator expression into buf.  To avoid problems around
+ * priority of operations, we always parenthesize the arguments.  Also we use
+ * OPERATOR(schema.operator) notation to determine remote operator exactly.
+ */
+static void
+deparseOpExpr(StringInfo buf,
+			  OpExpr *node,
+			  PlannerInfo *root)
+{
+	HeapTuple	tuple;
+	Form_pg_operator form;
+	const char *opnspname;
+	char	   *opname;
+	char		oprkind;
+	ListCell   *arg;
+
+	/* Retrieve necessary information about the operator from system catalog. */
+	tuple = SearchSysCache1(OPEROID, ObjectIdGetDatum(node->opno));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for operator %u", node->opno);
+	form = (Form_pg_operator) GETSTRUCT(tuple);
+	opnspname = quote_identifier(get_namespace_name(form->oprnamespace));
+	/* opname is not a SQL identifier, so we don't need to quote it. */
+	opname = NameStr(form->oprname);
+	oprkind = form->oprkind;
+	ReleaseSysCache(tuple);
+
+	/* Sanity check. */
+	Assert((oprkind == 'r' && list_length(node->args) == 1) ||
+		   (oprkind == 'l' && list_length(node->args) == 1) ||
+		   (oprkind == 'b' && list_length(node->args) == 2));
+
+	/* Always parenthesize the expression. */
+	appendStringInfoChar(buf, '(');
+
+	/* Deparse first operand. */
+	arg = list_head(node->args);
+	if (oprkind == 'r' || oprkind == 'b')
+	{
+		deparseExpr(buf, lfirst(arg), root);
+		appendStringInfoChar(buf, ' ');
+	}
+
+	/* Deparse fully qualified operator name. */
+	appendStringInfo(buf, "OPERATOR(%s.%s)", opnspname, opname);
+
+	/* Deparse last operand. */
+	arg = list_tail(node->args);
+	if (oprkind == 'l' || oprkind == 'b')
+	{
+		appendStringInfoChar(buf, ' ');
+		deparseExpr(buf, lfirst(arg), root);
+	}
+
+	appendStringInfoChar(buf, ')');
+}
+
+static void
+deparseArrayRef(StringInfo buf,
+				ArrayRef *node,
+				PlannerInfo *root)
+{
+	ListCell	   *lowlist_item;
+	ListCell	   *uplist_item;
+
+	/* Always parenthesize the expression. */
+	appendStringInfoChar(buf, '(');
+
+	/* Deparse referenced array expression first. */
+	appendStringInfoChar(buf, '(');
+	deparseExpr(buf, node->refexpr, root);
+	appendStringInfoChar(buf, ')');
+
+	/* Deparse subscripts expression. */
+	lowlist_item = list_head(node->reflowerindexpr);	/* could be NULL */
+	foreach(uplist_item, node->refupperindexpr)
+	{
+		appendStringInfoChar(buf, '[');
+		if (lowlist_item)
+		{
+			deparseExpr(buf, lfirst(lowlist_item), root);
+			appendStringInfoChar(buf, ':');
+			lowlist_item = lnext(lowlist_item);
+		}
+		deparseExpr(buf, lfirst(uplist_item), root);
+		appendStringInfoChar(buf, ']');
+	}
+
+	appendStringInfoChar(buf, ')');
+}
+
+
+/*
+ * Deparse given array of something into buf.
+ */
+static void
+deparseArrayExpr(StringInfo buf,
+				 ArrayExpr *node,
+				 PlannerInfo *root)
+{
+	ListCell	   *lc;
+	bool			first = true;
+
+	appendStringInfo(buf, "ARRAY[");
+	foreach(lc, node->elements)
+	{
+		if (!first)
+			appendStringInfo(buf, ", ");
+		deparseExpr(buf, lfirst(lc), root);
+
+		first = false;
+	}
+	appendStringInfoChar(buf, ']');
+
+	/* If the array is empty, we need explicit cast to the array type. */
+	if (node->elements == NIL)
+	{
+		char	   *typname;
+
+		typname = format_type_with_typemod(node->array_typeid, -1);
+		appendStringInfo(buf, "::%s", typname);
+	}
+}
+
+/*
+ * Returns true if given expr is safe to evaluate on the foreign server.  If
+ * result is true, extra information has_param tells whether given expression
+ * contains any Param node.  This is useful to determine whether the expression
+ * can be used in remote EXPLAIN.
+ */
+static bool
+is_foreign_expr(PlannerInfo *root,
+				RelOptInfo *baserel,
+				Expr *expr,
+				bool *has_param)
+{
+	foreign_executable_cxt	context;
+	context.root = root;
+	context.foreignrel = baserel;
+	context.has_param = false;
+
+	/*
+	 * 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;
+
+	/*
+	 * Tell caller whether the given expression contains any Param node, which
+	 * can't be used in EXPLAIN statement before executor starts.
+	 */
+	*has_param = context.has_param;
+
+	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)
+{
+	if (node == NULL)
+		return false;
+
+	/*
+	 * Special case handling for List; expression_tree_walker handles List as
+	 * well as other Expr nodes.  For instance, List is used in RestrictInfo
+	 * for args of FuncExpr node.
+	 *
+	 * Although the comments of expression_tree_walker mention that
+	 * RangeTblRef, FromExpr, JoinExpr, and SetOperationStmt are handled as
+	 * well, but we don't care them because they are not used in RestrictInfo.
+	 * If one of them was passed into, default label catches it and give up
+	 * traversing.
+	 */
+	if (IsA(node, List))
+	{
+		ListCell	   *lc;
+
+		foreach(lc, (List *) node)
+		{
+			if (foreign_expr_walker(lfirst(lc), context))
+				return true;
+		}
+		return false;
+	}
+
+	/*
+	 * 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;
+
+	switch (nodeTag(node))
+	{
+		case T_Const:
+			/*
+			 * Using anyarray and/or anyenum in remote query is not supported.
+			 */
+			if (((Const *) node)->consttype == ANYARRAYOID ||
+				((Const *) node)->consttype == ANYNONARRAYOID)
+				return true;
+			break;
+		case T_BoolExpr:
+		case T_NullTest:
+		case T_DistinctExpr:
+		case T_RelabelType:
+			/*
+			 * 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;
+		/*
+		 * 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.
+		 */
+		case T_FuncExpr:
+			{
+				FuncExpr	   *fe = (FuncExpr *) node;
+				if (!is_builtin(fe->funcid))
+					return true;
+			}
+			break;
+		case T_Param:
+			/*
+			 * Only external parameters can be pushed down.:
+			 */
+			{
+				if (((Param *) node)->paramkind != PARAM_EXTERN)
+					return true;
+
+				/* Mark that this expression contains Param node. */
+				context->has_param = 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) || !is_builtin(oe->opfuncid))
+					return true;
+
+				/*
+				 * If the operator takes collatable type as operands, we push
+				 * down only "=" and "<>" which are not affected by collation.
+				 * Other operators might be safe about collation, but these two
+				 * seem enough to cover practical use cases.
+				 */
+				if (exprInputCollation(node) != InvalidOid)
+				{
+					char   *opname = get_opname(oe->opno);
+
+					if (strcmp(opname, "=") != 0 && strcmp(opname, "<>") != 0)
+						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) || !is_builtin(oe->opfuncid))
+					return true;
+
+				/*
+				 * If the operator takes collatable type as operands, we push
+				 * down only "=" and "<>" which are not affected by collation.
+				 * Other operators might be safe about collation, but these two
+				 * seem enough to cover practical use cases.
+				 */
+				if (exprInputCollation(node) != InvalidOid)
+				{
+					char   *opname = get_opname(oe->opno);
+
+					if (strcmp(opname, "=") != 0 && strcmp(opname, "<>") != 0)
+						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.
+			 */
+			{
+				ArrayRef	   *ar = (ArrayRef *) node;;
+
+				if (!is_builtin(ar->refelemtype))
+					return true;
+
+				/* Assignment should not be in restrictions. */
+				if (ar->refassgnexpr != NULL)
+					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);
+}
+
+/*
+ * Deparse WHERE clause from given list of RestrictInfo and append them to buf.
+ * We assume that buf already holds a SQL statement which ends with valid WHERE
+ * clause.
+ *
+ * Only when calling the first time for a statement, is_first should be true.
+ */
+void
+appendWhereClause(StringInfo buf,
+				  bool is_first,
+				  List *exprs,
+				  PlannerInfo *root)
+{
+	bool			first = true;
+	ListCell	   *lc;
+
+	foreach(lc, exprs)
+	{
+		RestrictInfo   *ri = (RestrictInfo *) lfirst(lc);
+
+		/* Connect expressions with "AND" and parenthesize whole condition. */
+		if (is_first && first)
+			appendStringInfo(buf, " WHERE ");
+		else
+			appendStringInfo(buf, " AND ");
+
+		appendStringInfoChar(buf, '(');
+		deparseExpr(buf, ri->clause, root);
+		appendStringInfoChar(buf, ')');
+
+		first = false;
+	}
+}
diff --git a/contrib/pgsql_fdw/expected/pgsql_fdw.out b/contrib/pgsql_fdw/expected/pgsql_fdw.out
index 1a20874..aea7b41 100644
--- a/contrib/pgsql_fdw/expected/pgsql_fdw.out
+++ b/contrib/pgsql_fdw/expected/pgsql_fdw.out
@@ -217,11 +217,11 @@ SELECT * FROM ft1 t1 ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
 
 -- with WHERE clause
 EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1';
-                                 QUERY PLAN                                  
------------------------------------------------------------------------------
+                                                                             QUERY PLAN                                                                             
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------
  Foreign Scan on ft1 t1
-   Filter: ((c7 >= '1'::bpchar) AND (c1 = 101) AND ((c6)::text = '1'::text))
-   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1"
+   Filter: (c7 >= '1'::bpchar)
+   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) 101)) AND (((c6)::text OPERATOR(pg_catalog.=) '1'::text))
 (3 rows)
 
 SELECT * FROM ft1 t1 WHERE t1.c1 = 101 AND t1.c6 = '1' AND t1.c7 >= '1';
@@ -329,20 +329,91 @@ EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 === t1.c2;
 (3 rows)
 
 EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = abs(t1.c2);
-                             QUERY PLAN                              
----------------------------------------------------------------------
+                                                          QUERY PLAN                                                           
+-------------------------------------------------------------------------------------------------------------------------------
  Foreign Scan on ft1 t1
-   Filter: (c1 = abs(c2))
-   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1"
-(3 rows)
+   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) pg_catalog.abs(c2)))
+(2 rows)
 
 EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = t1.c2;
-                             QUERY PLAN                              
----------------------------------------------------------------------
+                                                  QUERY PLAN                                                   
+---------------------------------------------------------------------------------------------------------------
  Foreign Scan on ft1 t1
-   Filter: (c1 = c2)
-   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1"
-(3 rows)
+   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) c2))
+(2 rows)
+
+-- ===================================================================
+-- WHERE push down
+-- ===================================================================
+EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 1;         -- Var, OpExpr(b), Const
+                                                  QUERY PLAN                                                  
+--------------------------------------------------------------------------------------------------------------
+ Foreign Scan on ft1 t1
+   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) 1))
+(2 rows)
+
+EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 100 AND t1.c2 = 0; -- BoolExpr
+                                                                     QUERY PLAN                                                                     
+----------------------------------------------------------------------------------------------------------------------------------------------------
+ Foreign Scan on ft1 t1
+   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) 100)) AND ((c2 OPERATOR(pg_catalog.=) 0))
+(2 rows)
+
+EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE c1 IS NULL;        -- NullTest
+                                         QUERY PLAN                                          
+---------------------------------------------------------------------------------------------
+ Foreign Scan on ft1 t1
+   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" IS NULL))
+(2 rows)
+
+EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE c1 IS NOT NULL;    -- NullTest
+                                           QUERY PLAN                                            
+-------------------------------------------------------------------------------------------------
+ Foreign Scan on ft1 t1
+   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" IS NOT NULL))
+(2 rows)
+
+EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE round(abs(c1), 0) = 1; -- FuncExpr
+                                                                         QUERY PLAN                                                                         
+------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Foreign Scan on ft1 t1
+   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE ((pg_catalog.round(pg_catalog.abs("C 1"), 0) OPERATOR(pg_catalog.=) 1::numeric))
+(2 rows)
+
+EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE c1 = -c1;          -- OpExpr(l)
+                                                                QUERY PLAN                                                                 
+-------------------------------------------------------------------------------------------------------------------------------------------
+ Foreign Scan on ft1 t1
+   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) (OPERATOR(pg_catalog.-) "C 1")))
+(2 rows)
+
+EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE 1 = c1!;           -- OpExpr(r)
+                                                                   QUERY PLAN                                                                   
+------------------------------------------------------------------------------------------------------------------------------------------------
+ Foreign Scan on ft1 t1
+   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE ((1::numeric OPERATOR(pg_catalog.=) ("C 1" OPERATOR(pg_catalog.!))))
+(2 rows)
+
+EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE (c1 IS NOT NULL) IS DISTINCT FROM (c1 IS NOT NULL); -- DistinctExpr
+                                                              QUERY PLAN                                                              
+--------------------------------------------------------------------------------------------------------------------------------------
+ Foreign Scan on ft1 t1
+   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" IS NOT NULL) IS DISTINCT FROM ("C 1" IS NOT NULL))
+(2 rows)
+
+EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE c1 = ANY(ARRAY[c2, 1, c1 + 0]); -- ScalarArrayOpExpr
+                                                                           QUERY PLAN                                                                            
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Foreign Scan on ft1 t1
+   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) ANY (ARRAY[c2, 1, ("C 1" OPERATOR(pg_catalog.+) 0)])))
+(2 rows)
+
+EXPLAIN (COSTS false) SELECT * FROM ft1 ft WHERE c1 = (ARRAY[c1,c2,3])[1]; -- ArrayRef
+                                                              QUERY PLAN                                                               
+---------------------------------------------------------------------------------------------------------------------------------------
+ Foreign Scan on ft1 ft
+   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) ((ARRAY["C 1", c2, 3])[1])))
+(2 rows)
 
 -- ===================================================================
 -- parameterized queries
@@ -350,17 +421,14 @@ EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = t1.c2;
 -- 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                                         
--------------------------------------------------------------------------------------------
+                                                          QUERY PLAN                                                          
+------------------------------------------------------------------------------------------------------------------------------
  Nested Loop
    ->  Foreign Scan on ft1 t1
-         Filter: (c1 = 1)
-         Remote SQL: 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: SELECT "C 1", NULL, c3, NULL, NULL, NULL, NULL FROM "S 1"."T 1"
-(8 rows)
+         Remote SQL: SELECT "C 1", NULL, c3, NULL, NULL, NULL, NULL FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) 1))
+   ->  Foreign Scan on ft2 t2
+         Remote SQL: SELECT "C 1", NULL, c3, NULL, NULL, NULL, NULL FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) 2))
+(5 rows)
 
 EXECUTE st1(1, 1);
   c3   |  c3   
@@ -377,20 +445,19 @@ EXECUTE st1(101, 101);
 -- 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                                           
-------------------------------------------------------------------------------------------------
+                                                               QUERY PLAN                                                                
+-----------------------------------------------------------------------------------------------------------------------------------------
  Sort
    Sort Key: t1.c1
    ->  Nested Loop Semi Join
          Join Filter: (t1.c3 = t2.c3)
          ->  Foreign Scan on ft1 t1
-               Filter: (c1 < 20)
-               Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1"
+               Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.<) 20))
          ->  Materialize
                ->  Foreign Scan on ft2 t2
-                     Filter: ((c1 > 10) AND (date_part('dow'::text, c4) = 6::double precision))
-                     Remote SQL: SELECT "C 1", NULL, c3, c4, NULL, NULL, NULL FROM "S 1"."T 1"
-(11 rows)
+                     Filter: (date_part('dow'::text, c4) = 6::double precision)
+                     Remote SQL: SELECT "C 1", NULL, c3, c4, NULL, NULL, NULL FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.>) 10))
+(10 rows)
 
 EXECUTE st2(10, 20);
  c1 | c2 |  c3   |              c4              |            c5            | c6 |     c7     
@@ -407,20 +474,18 @@ EXECUTE st1(101, 101);
 -- 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                                           
-------------------------------------------------------------------------------------------------
+                                                                                                            QUERY PLAN                                                                                                            
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  Sort
    Sort Key: t1.c1
    ->  Nested Loop Semi Join
          Join Filter: (t1.c3 = t2.c3)
          ->  Foreign Scan on ft1 t1
-               Filter: (c1 < 20)
-               Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1"
+               Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.<) 20))
          ->  Materialize
                ->  Foreign Scan on ft2 t2
-                     Filter: ((c1 > 10) AND (date_part('dow'::text, c5) = 6::double precision))
-                     Remote SQL: SELECT "C 1", NULL, c3, NULL, c5, NULL, NULL FROM "S 1"."T 1"
-(11 rows)
+                     Remote SQL: SELECT "C 1", NULL, c3, NULL, c5, NULL, NULL FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.>) 10)) AND ((pg_catalog.date_part('dow'::text, c5) OPERATOR(pg_catalog.=) 6::double precision))
+(9 rows)
 
 EXECUTE st3(10, 20);
  c1 | c2 |  c3   |              c4              |            c5            | c6 |     c7     
@@ -437,52 +502,46 @@ EXECUTE st3(20, 30);
 -- 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                              
----------------------------------------------------------------------
+                                                  QUERY PLAN                                                  
+--------------------------------------------------------------------------------------------------------------
  Foreign Scan on ft1 t1
-   Filter: (c1 = 1)
-   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1"
-(3 rows)
+   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) 1))
+(2 rows)
 
 EXPLAIN (COSTS false) EXECUTE st4(1);
-                             QUERY PLAN                              
----------------------------------------------------------------------
+                                                  QUERY PLAN                                                  
+--------------------------------------------------------------------------------------------------------------
  Foreign Scan on ft1 t1
-   Filter: (c1 = 1)
-   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1"
-(3 rows)
+   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) 1))
+(2 rows)
 
 EXPLAIN (COSTS false) EXECUTE st4(1);
-                             QUERY PLAN                              
----------------------------------------------------------------------
+                                                  QUERY PLAN                                                  
+--------------------------------------------------------------------------------------------------------------
  Foreign Scan on ft1 t1
-   Filter: (c1 = 1)
-   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1"
-(3 rows)
+   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) 1))
+(2 rows)
 
 EXPLAIN (COSTS false) EXECUTE st4(1);
-                             QUERY PLAN                              
----------------------------------------------------------------------
+                                                  QUERY PLAN                                                  
+--------------------------------------------------------------------------------------------------------------
  Foreign Scan on ft1 t1
-   Filter: (c1 = 1)
-   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1"
-(3 rows)
+   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) 1))
+(2 rows)
 
 EXPLAIN (COSTS false) EXECUTE st4(1);
-                             QUERY PLAN                              
----------------------------------------------------------------------
+                                                  QUERY PLAN                                                  
+--------------------------------------------------------------------------------------------------------------
  Foreign Scan on ft1 t1
-   Filter: (c1 = 1)
-   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1"
-(3 rows)
+   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) 1))
+(2 rows)
 
 EXPLAIN (COSTS false) EXECUTE st4(1);
-                             QUERY PLAN                              
----------------------------------------------------------------------
+                                                  QUERY PLAN                                                   
+---------------------------------------------------------------------------------------------------------------
  Foreign Scan on ft1 t1
-   Filter: (c1 = $1)
-   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1"
-(3 rows)
+   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7 FROM "S 1"."T 1" WHERE (("C 1" OPERATOR(pg_catalog.=) $1))
+(2 rows)
 
 -- cleanup
 DEALLOCATE st1;
diff --git a/contrib/pgsql_fdw/pgsql_fdw.c b/contrib/pgsql_fdw/pgsql_fdw.c
index 1b7db4d..b7885a8 100644
--- a/contrib/pgsql_fdw/pgsql_fdw.c
+++ b/contrib/pgsql_fdw/pgsql_fdw.c
@@ -64,6 +64,9 @@ typedef struct PgsqlFdwPlanState {
 	StringInfoData	sql;
 	Cost			startup_cost;
 	Cost			total_cost;
+	List		   *remote_conds;
+	List		   *param_conds;
+	List		   *local_conds;
 
 	/* Cached catalog information. */
 	ForeignTable   *table;
@@ -252,6 +255,9 @@ pgsqlGetForeignRelSize(PlannerInfo *root,
 	int				width;
 	Cost			startup_cost;
 	Cost			total_cost;
+	List		   *remote_conds = NIL;
+	List		   *param_conds = NIL;
+	List		   *local_conds = NIL;
 	Selectivity		sel;
 
 	/*
@@ -268,23 +274,35 @@ pgsqlGetForeignRelSize(PlannerInfo *root,
 	user = GetUserMapping(GetOuterUserId(), server->serverid);
 
 	/*
-	 * Create plain SELECT statement with no WHERE clause for this scan in
-	 * order to obtain meaningful rows estimation by executing EXPLAIN on
-	 * remote server.
+	 * Construct remote query which consists of SELECT, FROM, and WHERE
+	 * clauses, but conditions contain any Param node are excluded because
+	 * placeholder can't be used in EXPLAIN statement.  Such conditions are
+	 * appended later.
 	 */
+	sortConditions(root, baserel, &remote_conds, &param_conds, &local_conds);
 	deparseSimpleSql(sql, foreigntableid, root, baserel);
+	if (list_length(remote_conds) > 0)
+		appendWhereClause(sql, true, remote_conds, root);
 	conn = GetConnection(server, user, false);
 	get_remote_estimate(sql->data, conn, &rows, &width,
 						&startup_cost, &total_cost);
 	ReleaseConnection(conn);
+	if (list_length(param_conds) > 0)
+		appendWhereClause(sql, !(list_length(remote_conds) > 0), param_conds,
+						  root);
 
 	/*
-	 * Estimate selectivity of local filtering by calling
-	 * clauselist_selectivity() against baserestrictinfo, and modify rows
-	 * estimate with it.
+	 * Estimate selectivity of conditions which are not used in remote EXPLAIN
+	 * by calling clauselist_selectivity().  The best we can do for
+	 * parameterized condition is to estimate selectivity on the basis of local
+	 * statistics.  When we actually obtain result rows, such conditions are
+	 * deparsed into remote query and reduce rows transferred.
 	 */
-	sel = clauselist_selectivity(root, baserel->baserestrictinfo,
-								 baserel->relid, JOIN_INNER, NULL);
+	sel = 1.0;
+	sel *= clauselist_selectivity(root, param_conds,
+								  baserel->relid, JOIN_INNER, NULL);
+	sel *= clauselist_selectivity(root, local_conds,
+								  baserel->relid, JOIN_INNER, NULL);
 	baserel->rows = rows * sel;
 	baserel->width = width;
 
@@ -294,6 +312,9 @@ pgsqlGetForeignRelSize(PlannerInfo *root,
 	 */
 	fpstate->startup_cost = startup_cost;
 	fpstate->total_cost = total_cost;
+	fpstate->remote_conds = remote_conds;
+	fpstate->param_conds = param_conds;
+	fpstate->local_conds = local_conds;
 	fpstate->table = table;
 	fpstate->server = server;
 	baserel->fdw_private = (void *) fpstate;
@@ -366,15 +387,22 @@ pgsqlGetForeignPlan(PlannerInfo *root,
 	PgsqlFdwPlanState *fpstate = (PgsqlFdwPlanState *) baserel->fdw_private;
 	Index			scan_relid = baserel->relid;
 	List		   *fdw_private = NIL;
+	List		   *fdw_exprs = NIL;
+	List		   *local_exprs = NIL;
+	ListCell	   *lc;
 
 	/*
-	 * We have no native ability to evaluate restriction clauses, so we just
-	 * put all the scan_clauses into the plan node's qual list for the
-	 * executor to check.  So all we have to do here is strip RestrictInfo
-	 * nodes from the clauses and ignore pseudoconstants (which will be
-	 * handled elsewhere).
+	 * We need lists of Expr other than the lists of RestrictInfo.  Now we can
+	 * merge remote_conds and param_conds into fdw_exprs, because they are
+	 * evaluated on remote side for actual remote query.
 	 */
-	scan_clauses = extract_actual_clauses(scan_clauses, false);
+	foreach(lc, fpstate->remote_conds)
+		fdw_exprs = lappend(fdw_exprs, ((RestrictInfo *) lfirst(lc))->clause);
+	foreach(lc, fpstate->param_conds)
+		fdw_exprs = lappend(fdw_exprs, ((RestrictInfo *) lfirst(lc))->clause);
+	foreach(lc, fpstate->local_conds)
+		local_exprs = lappend(local_exprs,
+							  ((RestrictInfo *) lfirst(lc))->clause);
 
 	/*
 	 * Make a list contains SELECT statement to it to executor with plan node
@@ -382,11 +410,20 @@ pgsqlGetForeignPlan(PlannerInfo *root,
 	 */
 	fdw_private = lappend(fdw_private, makeString(fpstate->sql.data));
 
-	/* Create the ForeignScan node with fdw_private of selected path. */
+	/*
+	 * Create the ForeignScan node from target list, local filtering
+	 * expressions, remote filtering expressions, and FDW private information.
+	 *
+	 * We remove expressions which are evaluated on remote side from qual of
+	 * the scan node to avoid redundant filtering.  Such filter reduction
+	 * can be done only here, done after choosing best path, because
+	 * baserestrictinfo in RelOptInfo is shared by all possible paths until
+	 * best path is chosen.
+	 */
 	return make_foreignscan(tlist,
-							scan_clauses,
+							local_exprs,
 							scan_relid,
-							NIL,
+							fdw_exprs,
 							fdw_private);
 }
 
diff --git a/contrib/pgsql_fdw/pgsql_fdw.h b/contrib/pgsql_fdw/pgsql_fdw.h
index 4e7a8e1..76c9f72 100644
--- a/contrib/pgsql_fdw/pgsql_fdw.h
+++ b/contrib/pgsql_fdw/pgsql_fdw.h
@@ -29,5 +29,14 @@ void deparseSimpleSql(StringInfo buf,
 					  Oid relid,
 					  PlannerInfo *root,
 					  RelOptInfo *baserel);
+void appendWhereClause(StringInfo buf,
+					   bool has_where,
+					   List *exprs,
+					   PlannerInfo *root);
+void sortConditions(PlannerInfo *root,
+					RelOptInfo *baserel,
+					List **remote_conds,
+					List **param_conds,
+					List **local_conds);
 
 #endif /* PGSQL_FDW_H */
diff --git a/contrib/pgsql_fdw/sql/pgsql_fdw.sql b/contrib/pgsql_fdw/sql/pgsql_fdw.sql
index da04568..003e168 100644
--- a/contrib/pgsql_fdw/sql/pgsql_fdw.sql
+++ b/contrib/pgsql_fdw/sql/pgsql_fdw.sql
@@ -178,6 +178,20 @@ EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = abs(t1.c2);
 EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = t1.c2;
 
 -- ===================================================================
+-- WHERE push down
+-- ===================================================================
+EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 1;         -- Var, OpExpr(b), Const
+EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE t1.c1 = 100 AND t1.c2 = 0; -- BoolExpr
+EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE c1 IS NULL;        -- NullTest
+EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE c1 IS NOT NULL;    -- NullTest
+EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE round(abs(c1), 0) = 1; -- FuncExpr
+EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE c1 = -c1;          -- OpExpr(l)
+EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE 1 = c1!;           -- OpExpr(r)
+EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE (c1 IS NOT NULL) IS DISTINCT FROM (c1 IS NOT NULL); -- DistinctExpr
+EXPLAIN (COSTS false) SELECT * FROM ft1 t1 WHERE c1 = ANY(ARRAY[c2, 1, c1 + 0]); -- ScalarArrayOpExpr
+EXPLAIN (COSTS false) SELECT * FROM ft1 ft WHERE c1 = (ARRAY[c1,c2,3])[1]; -- ArrayRef
+
+-- ===================================================================
 -- parameterized queries
 -- ===================================================================
 -- simple join