diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
index f005e0f26fb8309b7ccf21522ccdc908a72bc92e..1f4b2449be7e7cf18b62f529e7c845e5e1dcef59 100644
--- a/contrib/postgres_fdw/deparse.c
+++ b/contrib/postgres_fdw/deparse.c
@@ -331,12 +331,10 @@ foreign_expr_walker(Node *node, foreign_expr_cxt *context)
  * function or type defined in the information_schema.
  *
  * Our constraints for dealing with types are tighter than they are for
- * functions or operators: we want to accept only types that are in pg_catalog
- * (else format_type might incorrectly fail to schema-qualify their names),
- * and we want to be sure that the remote server will use the same OID as
- * we do (since we must transmit a numeric type OID when passing a value of
- * the type as a query parameter).  Both of these are reasons to reject
- * objects created post-bootstrap.
+ * functions or operators: we want to accept only types that are in pg_catalog,
+ * else format_type might incorrectly fail to schema-qualify their names.
+ * (This could be fixed with some changes to format_type, but for now there's
+ * no need.)  Thus we must exclude information_schema types.
  *
  * XXX there is a problem with this, which is that the set of built-in
  * objects expands over time.  Something that is built-in to us might not
@@ -794,12 +792,20 @@ deparseConst(StringInfo buf, Const *node, PlannerInfo *root)
  * We don't need to renumber the parameter ID, because the executor functions
  * in postgres_fdw.c preserve the numbering of PARAM_EXTERN Params.
  * (This might change soon.)
+ *
+ * Note: we label the Param's type explicitly rather than relying on
+ * transmitting a numeric type OID in PQexecParams().  This allows us to
+ * avoid assuming that types have the same OIDs on the remote side as they
+ * do locally --- they need only have the same names.
  */
 static void
 deparseParam(StringInfo buf, Param *node, PlannerInfo *root)
 {
 	Assert(node->paramkind == PARAM_EXTERN);
 	appendStringInfo(buf, "$%d", node->paramid);
+	appendStringInfo(buf, "::%s",
+					 format_type_with_typemod(node->paramtype,
+											  node->paramtypmod));
 }
 
 /*
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index b81a30867388a8b1614599560f4b8fe1fd6a0752..9a2c8e83cc4489b9e5aee6b3182fd87834fc621d 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -558,11 +558,11 @@ EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1);
 
 -- once we try it enough times, should switch to generic plan
 EXPLAIN (VERBOSE, COSTS false) EXECUTE st4(1);
-                                          QUERY PLAN                                          
-----------------------------------------------------------------------------------------------
+                                              QUERY PLAN                                               
+-------------------------------------------------------------------------------------------------------
  Foreign Scan on public.ft1 t1
    Output: c1, c2, c3, c4, c5, c6, c7, c8
-   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1))
+   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $1::integer))
 (3 rows)
 
 -- value of $1 should not be sent to remote
@@ -613,12 +613,12 @@ EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1);
 (4 rows)
 
 EXPLAIN (VERBOSE, COSTS false) EXECUTE st5('foo', 1);
-                                          QUERY PLAN                                          
-----------------------------------------------------------------------------------------------
+                                              QUERY PLAN                                               
+-------------------------------------------------------------------------------------------------------
  Foreign Scan on public.ft1 t1
    Output: c1, c2, c3, c4, c5, c6, c7, c8
    Filter: (t1.c8 = $1)
-   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $2))
+   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" = $2::integer))
 (4 rows)
 
 EXECUTE st5('foo', 1);
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 0aef00b738dfeeede00e50aef31c538e6312bda2..a3256179f2bee9db0446dbc7c3125177fe0fd3b4 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -901,11 +901,24 @@ create_cursor(ForeignScanState *node)
 			if (!OidIsValid(prm->ptype) && params->paramFetch != NULL)
 				params->paramFetch(params, paramno);
 
+			/*
+			 * Force the remote server to infer a type for this parameter.
+			 * Since we explicitly cast every parameter (see deparse.c), the
+			 * "inference" is trivial and will produce the desired result.
+			 * This allows us to avoid assuming that the remote server has the
+			 * same OIDs we do for the parameters' types.
+			 *
+			 * We'd not need to pass a type array to PQexecParams at all,
+			 * except that there may be unused holes in the array, which
+			 * will have to be filled with something or the remote server will
+			 * complain.  We arbitrarily set them to INT4OID earlier.
+			 */
+			types[paramno - 1] = InvalidOid;
+
 			/*
 			 * Get string representation of each parameter value by invoking
 			 * type-specific output function, unless the value is null.
 			 */
-			types[paramno - 1] = prm->ptype;
 			if (prm->isnull)
 				values[paramno - 1] = NULL;
 			else