diff --git a/contrib/file_fdw/input/file_fdw.source b/contrib/file_fdw/input/file_fdw.source
index 8e3d553f904b8edc2591c737e937dd12977e4e62..f7fd28d44d7be3a67158d16e4790284c8d280f91 100644
--- a/contrib/file_fdw/input/file_fdw.source
+++ b/contrib/file_fdw/input/file_fdw.source
@@ -118,7 +118,6 @@ SELECT tableoid::regclass, b FROM agg_csv;
 INSERT INTO agg_csv VALUES(1,2.0);
 UPDATE agg_csv SET a = 1;
 DELETE FROM agg_csv WHERE a = 100;
-SELECT * FROM agg_csv FOR UPDATE OF agg_csv;
 -- but this should be ignored
 SELECT * FROM agg_csv FOR UPDATE;
 
diff --git a/contrib/file_fdw/output/file_fdw.source b/contrib/file_fdw/output/file_fdw.source
index ece72429ba5f1fb92e9fc21051190305b59047b2..4f90baebd6b09b22535affde9fde3a0c40bdf5b0 100644
--- a/contrib/file_fdw/output/file_fdw.source
+++ b/contrib/file_fdw/output/file_fdw.source
@@ -185,15 +185,11 @@ SELECT tableoid::regclass, b FROM agg_csv;
 
 -- updates aren't supported
 INSERT INTO agg_csv VALUES(1,2.0);
-ERROR:  cannot change foreign table "agg_csv"
+ERROR:  cannot insert into foreign table "agg_csv"
 UPDATE agg_csv SET a = 1;
-ERROR:  cannot change foreign table "agg_csv"
+ERROR:  cannot update foreign table "agg_csv"
 DELETE FROM agg_csv WHERE a = 100;
-ERROR:  cannot change foreign table "agg_csv"
-SELECT * FROM agg_csv FOR UPDATE OF agg_csv;
-ERROR:  row-level locks cannot be used with foreign table "agg_csv"
-LINE 1: SELECT * FROM agg_csv FOR UPDATE OF agg_csv;
-                                            ^
+ERROR:  cannot delete from foreign table "agg_csv"
 -- but this should be ignored
 SELECT * FROM agg_csv FOR UPDATE;
   a  |    b    
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 32a3138ce0f0424b6a368195f815c58d0e3d0bae..22ac50e6f9f1ad6cf8fe2c62ed7c9fded52e9002 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -47,6 +47,8 @@ typedef struct ConnCacheEntry
 	PGconn	   *conn;			/* connection to foreign server, or NULL */
 	int			xact_depth;		/* 0 = no xact open, 1 = main xact open, 2 =
 								 * one level of subxact open, etc */
+	bool		have_prep_stmt; /* have we prepared any stmts in this xact? */
+	bool		have_error;		/* have any subxacts aborted in this xact? */
 } ConnCacheEntry;
 
 /*
@@ -54,8 +56,9 @@ typedef struct ConnCacheEntry
  */
 static HTAB *ConnectionHash = NULL;
 
-/* for assigning cursor numbers */
+/* for assigning cursor numbers and prepared statement numbers */
 static unsigned int cursor_number = 0;
+static unsigned int prep_stmt_number = 0;
 
 /* tracks whether any work is needed in callback functions */
 static bool xact_got_connection = false;
@@ -78,6 +81,10 @@ static void pgfdw_subxact_callback(SubXactEvent event,
  * if we don't already have a suitable one, and a transaction is opened at
  * the right subtransaction nesting depth if we didn't do that already.
  *
+ * will_prep_stmt must be true if caller intends to create any prepared
+ * statements.	Since those don't go away automatically at transaction end
+ * (not even on error), we need this flag to cue manual cleanup.
+ *
  * XXX Note that caching connections theoretically requires a mechanism to
  * detect change of FDW objects to invalidate already established connections.
  * We could manage that by watching for invalidation events on the relevant
@@ -86,7 +93,8 @@ static void pgfdw_subxact_callback(SubXactEvent event,
  * mid-transaction anyway.
  */
 PGconn *
-GetConnection(ForeignServer *server, UserMapping *user)
+GetConnection(ForeignServer *server, UserMapping *user,
+			  bool will_prep_stmt)
 {
 	bool		found;
 	ConnCacheEntry *entry;
@@ -131,6 +139,8 @@ GetConnection(ForeignServer *server, UserMapping *user)
 		/* initialize new hashtable entry (key is already filled in) */
 		entry->conn = NULL;
 		entry->xact_depth = 0;
+		entry->have_prep_stmt = false;
+		entry->have_error = false;
 	}
 
 	/*
@@ -147,6 +157,8 @@ GetConnection(ForeignServer *server, UserMapping *user)
 	if (entry->conn == NULL)
 	{
 		entry->xact_depth = 0;	/* just to be sure */
+		entry->have_prep_stmt = false;
+		entry->have_error = false;
 		entry->conn = connect_pg_server(server, user);
 		elog(DEBUG3, "new postgres_fdw connection %p for server \"%s\"",
 			 entry->conn, server->servername);
@@ -157,6 +169,9 @@ GetConnection(ForeignServer *server, UserMapping *user)
 	 */
 	begin_remote_xact(entry);
 
+	/* Remember if caller will prepare statements */
+	entry->have_prep_stmt |= will_prep_stmt;
+
 	return entry->conn;
 }
 
@@ -393,6 +408,20 @@ GetCursorNumber(PGconn *conn)
 	return ++cursor_number;
 }
 
+/*
+ * Assign a "unique" number for a prepared statement.
+ *
+ * This works much like GetCursorNumber, except that we never reset the counter
+ * within a session.  That's because we can't be 100% sure we've gotten rid
+ * of all prepared statements on all connections, and it's not really worth
+ * increasing the risk of prepared-statement name collisions by resetting.
+ */
+unsigned int
+GetPrepStmtNumber(PGconn *conn)
+{
+	return ++prep_stmt_number;
+}
+
 /*
  * Report an error we got from the remote server.
  *
@@ -400,6 +429,10 @@ GetCursorNumber(PGconn *conn)
  * res: PGresult containing the error
  * clear: if true, PQclear the result (otherwise caller will handle it)
  * sql: NULL, or text of remote command we tried to execute
+ *
+ * Note: callers that choose not to throw ERROR for a remote error are
+ * responsible for making sure that the associated ConnCacheEntry gets
+ * marked with have_error = true.
  */
 void
 pgfdw_report_error(int elevel, PGresult *res, bool clear, const char *sql)
@@ -480,6 +513,22 @@ pgfdw_xact_callback(XactEvent event, void *arg)
 				if (PQresultStatus(res) != PGRES_COMMAND_OK)
 					pgfdw_report_error(ERROR, res, true, "COMMIT TRANSACTION");
 				PQclear(res);
+
+				/*
+				 * If there were any errors in subtransactions, and we made
+				 * prepared statements, do a DEALLOCATE ALL to make sure we
+				 * get rid of all prepared statements.	This is annoying and
+				 * not terribly bulletproof, but it's probably not worth
+				 * trying harder.  We intentionally ignore any errors in the
+				 * DEALLOCATE.
+				 */
+				if (entry->have_prep_stmt && entry->have_error)
+				{
+					res = PQexec(entry->conn, "DEALLOCATE ALL");
+					PQclear(res);
+				}
+				entry->have_prep_stmt = false;
+				entry->have_error = false;
 				break;
 			case XACT_EVENT_PRE_PREPARE:
 
@@ -502,6 +551,8 @@ pgfdw_xact_callback(XactEvent event, void *arg)
 				elog(ERROR, "missed cleaning up connection during pre-commit");
 				break;
 			case XACT_EVENT_ABORT:
+				/* Assume we might have lost track of prepared statements */
+				entry->have_error = true;
 				/* If we're aborting, abort all remote transactions too */
 				res = PQexec(entry->conn, "ABORT TRANSACTION");
 				/* Note: can't throw ERROR, it would be infinite loop */
@@ -509,7 +560,17 @@ pgfdw_xact_callback(XactEvent event, void *arg)
 					pgfdw_report_error(WARNING, res, true,
 									   "ABORT TRANSACTION");
 				else
+				{
 					PQclear(res);
+					/* As above, make sure we've cleared any prepared stmts */
+					if (entry->have_prep_stmt && entry->have_error)
+					{
+						res = PQexec(entry->conn, "DEALLOCATE ALL");
+						PQclear(res);
+					}
+					entry->have_prep_stmt = false;
+					entry->have_error = false;
+				}
 				break;
 		}
 
@@ -593,6 +654,8 @@ pgfdw_subxact_callback(SubXactEvent event, SubTransactionId mySubid,
 		}
 		else
 		{
+			/* Assume we might have lost track of prepared statements */
+			entry->have_error = true;
 			/* Rollback all remote subtransactions during abort */
 			snprintf(sql, sizeof(sql),
 					 "ROLLBACK TO SAVEPOINT s%d; RELEASE SAVEPOINT s%d",
diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
index 9816f550ca57cd9af4e07bc2f2bc1c96cde50ec7..d667c997609bee89adb09b2f1c041c70dba0bd82 100644
--- a/contrib/postgres_fdw/deparse.c
+++ b/contrib/postgres_fdw/deparse.c
@@ -25,6 +25,7 @@
 
 #include "postgres_fdw.h"
 
+#include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/sysattr.h"
 #include "access/transam.h"
@@ -66,6 +67,14 @@ static bool is_builtin(Oid procid);
 /*
  * Functions to construct string representation of a node tree.
  */
+static void deparseTargetList(StringInfo buf,
+				  PlannerInfo *root,
+				  Index rtindex,
+				  Relation rel,
+				  Bitmapset *attrs_used);
+static void deparseReturningList(StringInfo buf, PlannerInfo *root,
+					 Index rtindex, Relation rel,
+					 List *returningList);
 static void deparseColumnRef(StringInfo buf, int varno, int varattno,
 				 PlannerInfo *root);
 static void deparseRelation(StringInfo buf, Oid relid);
@@ -349,80 +358,104 @@ is_builtin(Oid oid)
 
 
 /*
- * Construct a simple SELECT statement that retrieves interesting columns
+ * Construct a simple SELECT statement that retrieves desired columns
  * of the specified foreign table, and append it to "buf".	The output
  * contains just "SELECT ... FROM tablename".
- *
- * "Interesting" columns are those appearing in the rel's targetlist or
- * in local_conds (conditions which can't be executed remotely).
  */
 void
-deparseSimpleSql(StringInfo buf,
+deparseSelectSql(StringInfo buf,
 				 PlannerInfo *root,
 				 RelOptInfo *baserel,
-				 List *local_conds)
+				 Bitmapset *attrs_used)
 {
-	RangeTblEntry *rte = root->simple_rte_array[baserel->relid];
-	Bitmapset  *attrs_used = NULL;
-	bool		have_wholerow;
-	bool		first;
-	AttrNumber	attr;
-	ListCell   *lc;
+	RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root);
+	Relation	rel;
 
-	/* Collect all the attributes needed for joins or final output. */
-	pull_varattnos((Node *) baserel->reltargetlist, baserel->relid,
-				   &attrs_used);
+	/*
+	 * Core code already has some lock on each rel being planned, so we can
+	 * use NoLock here.
+	 */
+	rel = heap_open(rte->relid, NoLock);
 
-	/* Add all the attributes used by local_conds. */
-	foreach(lc, local_conds)
-	{
-		RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+	/*
+	 * Construct SELECT list
+	 */
+	appendStringInfoString(buf, "SELECT ");
+	deparseTargetList(buf, root, baserel->relid, rel, attrs_used);
 
-		pull_varattnos((Node *) rinfo->clause, baserel->relid,
-					   &attrs_used);
-	}
+	/*
+	 * Construct FROM clause
+	 */
+	appendStringInfoString(buf, " FROM ");
+	deparseRelation(buf, RelationGetRelid(rel));
+
+	heap_close(rel, NoLock);
+}
+
+/*
+ * Emit a target list that retrieves the columns specified in attrs_used.
+ * This is used for both SELECT and RETURNING targetlists.
+ *
+ * We list attributes in order of the foreign table's columns, but replace
+ * any attributes that need not be fetched with NULL constants.  (We can't
+ * just omit such attributes, or we'll lose track of which columns are
+ * which at runtime.)  Note however that any dropped columns are ignored.
+ * Also, if ctid needs to be retrieved, it's added at the end.
+ */
+static void
+deparseTargetList(StringInfo buf,
+				  PlannerInfo *root,
+				  Index rtindex,
+				  Relation rel,
+				  Bitmapset *attrs_used)
+{
+	TupleDesc	tupdesc = RelationGetDescr(rel);
+	bool		have_wholerow;
+	bool		first;
+	int			i;
 
 	/* If there's a whole-row reference, we'll need all the columns. */
 	have_wholerow = bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
 								  attrs_used);
 
-	/*
-	 * Construct SELECT list
-	 *
-	 * We list attributes in order of the foreign table's columns, but replace
-	 * any attributes that need not be fetched with NULL constants. (We can't
-	 * just omit such attributes, or we'll lose track of which columns are
-	 * which at runtime.)  Note however that any dropped columns are ignored.
-	 */
-	appendStringInfo(buf, "SELECT ");
 	first = true;
-	for (attr = 1; attr <= baserel->max_attr; attr++)
+	for (i = 1; i <= tupdesc->natts; i++)
 	{
+		Form_pg_attribute attr = tupdesc->attrs[i - 1];
+
 		/* Ignore dropped attributes. */
-		if (get_rte_attribute_is_dropped(rte, attr))
+		if (attr->attisdropped)
 			continue;
 
 		if (!first)
-			appendStringInfo(buf, ", ");
+			appendStringInfoString(buf, ", ");
 		first = false;
 
 		if (have_wholerow ||
-			bms_is_member(attr - FirstLowInvalidHeapAttributeNumber,
+			bms_is_member(i - FirstLowInvalidHeapAttributeNumber,
 						  attrs_used))
-			deparseColumnRef(buf, baserel->relid, attr, root);
+			deparseColumnRef(buf, rtindex, i, root);
 		else
-			appendStringInfo(buf, "NULL");
+			appendStringInfoString(buf, "NULL");
 	}
 
-	/* Don't generate bad syntax if no undropped columns */
-	if (first)
-		appendStringInfo(buf, "NULL");
-
 	/*
-	 * Construct FROM clause
+	 * Add ctid if needed.	We currently don't support retrieving any other
+	 * system columns.
 	 */
-	appendStringInfo(buf, " FROM ");
-	deparseRelation(buf, rte->relid);
+	if (bms_is_member(SelfItemPointerAttributeNumber - FirstLowInvalidHeapAttributeNumber,
+					  attrs_used))
+	{
+		if (!first)
+			appendStringInfoString(buf, ", ");
+		first = false;
+
+		appendStringInfoString(buf, "ctid");
+	}
+
+	/* Don't generate bad syntax if no undropped columns */
+	if (first)
+		appendStringInfoString(buf, "NULL");
 }
 
 /*
@@ -432,9 +465,9 @@ deparseSimpleSql(StringInfo buf,
  */
 void
 appendWhereClause(StringInfo buf,
-				  bool is_first,
+				  PlannerInfo *root,
 				  List *exprs,
-				  PlannerInfo *root)
+				  bool is_first)
 {
 	ListCell   *lc;
 
@@ -444,9 +477,9 @@ appendWhereClause(StringInfo buf,
 
 		/* Connect expressions with "AND" and parenthesize each condition. */
 		if (is_first)
-			appendStringInfo(buf, " WHERE ");
+			appendStringInfoString(buf, " WHERE ");
 		else
-			appendStringInfo(buf, " AND ");
+			appendStringInfoString(buf, " AND ");
 
 		appendStringInfoChar(buf, '(');
 		deparseExpr(buf, ri->clause, root);
@@ -456,6 +489,147 @@ appendWhereClause(StringInfo buf,
 	}
 }
 
+/*
+ * deparse remote INSERT statement
+ */
+void
+deparseInsertSql(StringInfo buf, PlannerInfo *root, Index rtindex,
+				 List *targetAttrs, List *returningList)
+{
+	RangeTblEntry *rte = planner_rt_fetch(rtindex, root);
+	Relation	rel = heap_open(rte->relid, NoLock);
+	TupleDesc	tupdesc = RelationGetDescr(rel);
+	AttrNumber	pindex;
+	bool		first;
+	ListCell   *lc;
+
+	appendStringInfoString(buf, "INSERT INTO ");
+	deparseRelation(buf, rte->relid);
+	appendStringInfoString(buf, "(");
+
+	first = true;
+	foreach(lc, targetAttrs)
+	{
+		int			attnum = lfirst_int(lc);
+		Form_pg_attribute attr = tupdesc->attrs[attnum - 1];
+
+		Assert(!attr->attisdropped);
+
+		if (!first)
+			appendStringInfoString(buf, ", ");
+		first = false;
+
+		deparseColumnRef(buf, rtindex, attnum, root);
+	}
+
+	appendStringInfoString(buf, ") VALUES (");
+
+	pindex = 1;
+	first = true;
+	foreach(lc, targetAttrs)
+	{
+		if (!first)
+			appendStringInfoString(buf, ", ");
+		first = false;
+
+		appendStringInfo(buf, "$%d", pindex);
+		pindex++;
+	}
+
+	appendStringInfoString(buf, ")");
+
+	if (returningList)
+		deparseReturningList(buf, root, rtindex, rel, returningList);
+
+	heap_close(rel, NoLock);
+}
+
+/*
+ * deparse remote UPDATE statement
+ */
+void
+deparseUpdateSql(StringInfo buf, PlannerInfo *root, Index rtindex,
+				 List *targetAttrs, List *returningList)
+{
+	RangeTblEntry *rte = planner_rt_fetch(rtindex, root);
+	Relation	rel = heap_open(rte->relid, NoLock);
+	TupleDesc	tupdesc = RelationGetDescr(rel);
+	AttrNumber	pindex;
+	bool		first;
+	ListCell   *lc;
+
+	appendStringInfoString(buf, "UPDATE ");
+	deparseRelation(buf, rte->relid);
+	appendStringInfoString(buf, " SET ");
+
+	pindex = 2;					/* ctid is always the first param */
+	first = true;
+	foreach(lc, targetAttrs)
+	{
+		int			attnum = lfirst_int(lc);
+		Form_pg_attribute attr = tupdesc->attrs[attnum - 1];
+
+		Assert(!attr->attisdropped);
+
+		if (!first)
+			appendStringInfoString(buf, ", ");
+		first = false;
+
+		deparseColumnRef(buf, rtindex, attnum, root);
+		appendStringInfo(buf, " = $%d", pindex);
+		pindex++;
+	}
+	appendStringInfoString(buf, " WHERE ctid = $1");
+
+	if (returningList)
+		deparseReturningList(buf, root, rtindex, rel, returningList);
+
+	heap_close(rel, NoLock);
+}
+
+/*
+ * deparse remote DELETE statement
+ */
+void
+deparseDeleteSql(StringInfo buf, PlannerInfo *root, Index rtindex,
+				 List *returningList)
+{
+	RangeTblEntry *rte = planner_rt_fetch(rtindex, root);
+
+	appendStringInfoString(buf, "DELETE FROM ");
+	deparseRelation(buf, rte->relid);
+	appendStringInfoString(buf, " WHERE ctid = $1");
+
+	if (returningList)
+	{
+		Relation	rel = heap_open(rte->relid, NoLock);
+
+		deparseReturningList(buf, root, rtindex, rel, returningList);
+		heap_close(rel, NoLock);
+	}
+}
+
+/*
+ * deparse RETURNING clause of INSERT/UPDATE/DELETE
+ */
+static void
+deparseReturningList(StringInfo buf, PlannerInfo *root,
+					 Index rtindex, Relation rel,
+					 List *returningList)
+{
+	Bitmapset  *attrs_used;
+
+	/*
+	 * We need the attrs mentioned in the query's RETURNING list.
+	 */
+	attrs_used = NULL;
+	pull_varattnos((Node *) returningList, rtindex,
+				   &attrs_used);
+
+	appendStringInfoString(buf, " RETURNING ");
+	deparseTargetList(buf, root, rtindex, rel, attrs_used);
+}
+
 /*
  * Construct SELECT statement to acquire size in blocks of given relation.
  *
@@ -495,13 +669,17 @@ deparseAnalyzeSql(StringInfo buf, Relation rel)
 	ListCell   *lc;
 	bool		first = true;
 
-	appendStringInfo(buf, "SELECT ");
+	appendStringInfoString(buf, "SELECT ");
 	for (i = 0; i < tupdesc->natts; i++)
 	{
 		/* Ignore dropped columns. */
 		if (tupdesc->attrs[i]->attisdropped)
 			continue;
 
+		if (!first)
+			appendStringInfoString(buf, ", ");
+		first = false;
+
 		/* Use attribute name or column_name option. */
 		colname = NameStr(tupdesc->attrs[i]->attname);
 		options = GetForeignColumnOptions(relid, i + 1);
@@ -517,20 +695,17 @@ deparseAnalyzeSql(StringInfo buf, Relation rel)
 			}
 		}
 
-		if (!first)
-			appendStringInfo(buf, ", ");
 		appendStringInfoString(buf, quote_identifier(colname));
-		first = false;
 	}
 
 	/* Don't generate bad syntax for zero-column relation. */
 	if (first)
-		appendStringInfo(buf, "NULL");
+		appendStringInfoString(buf, "NULL");
 
 	/*
 	 * Construct FROM clause
 	 */
-	appendStringInfo(buf, " FROM ");
+	appendStringInfoString(buf, " FROM ");
 	deparseRelation(buf, relid);
 }
 
@@ -547,10 +722,10 @@ deparseColumnRef(StringInfo buf, int varno, int varattno, PlannerInfo *root)
 	ListCell   *lc;
 
 	/* varno must not be any of OUTER_VAR, INNER_VAR and INDEX_VAR. */
-	Assert(varno >= 1 && varno <= root->simple_rel_array_size);
+	Assert(!IS_SPECIAL_VARNO(varno));
 
 	/* Get RangeTblEntry from array in PlannerInfo. */
-	rte = root->simple_rte_array[varno];
+	rte = planner_rt_fetch(varno, root);
 
 	/*
 	 * If it's a column of a foreign table, and it has the column_name FDW
@@ -608,8 +783,8 @@ deparseRelation(StringInfo buf, Oid relid)
 	}
 
 	/*
-	 * Note: we could skip printing the schema name if it's pg_catalog,
-	 * but that doesn't seem worth the trouble.
+	 * Note: we could skip printing the schema name if it's pg_catalog, but
+	 * that doesn't seem worth the trouble.
 	 */
 	if (nspname == NULL)
 		nspname = get_namespace_name(get_rel_namespace(relid));
@@ -1059,7 +1234,7 @@ deparseDistinctExpr(StringInfo buf, DistinctExpr *node, PlannerInfo *root)
 
 	appendStringInfoChar(buf, '(');
 	deparseExpr(buf, linitial(node->args), root);
-	appendStringInfo(buf, " IS DISTINCT FROM ");
+	appendStringInfoString(buf, " IS DISTINCT FROM ");
 	deparseExpr(buf, lsecond(node->args), root);
 	appendStringInfoChar(buf, ')');
 }
@@ -1146,7 +1321,7 @@ deparseBoolExpr(StringInfo buf, BoolExpr *node, PlannerInfo *root)
 			op = "OR";
 			break;
 		case NOT_EXPR:
-			appendStringInfo(buf, "(NOT ");
+			appendStringInfoString(buf, "(NOT ");
 			deparseExpr(buf, linitial(node->args), root);
 			appendStringInfoChar(buf, ')');
 			return;
@@ -1173,9 +1348,9 @@ deparseNullTest(StringInfo buf, NullTest *node, PlannerInfo *root)
 	appendStringInfoChar(buf, '(');
 	deparseExpr(buf, node->arg, root);
 	if (node->nulltesttype == IS_NULL)
-		appendStringInfo(buf, " IS NULL)");
+		appendStringInfoString(buf, " IS NULL)");
 	else
-		appendStringInfo(buf, " IS NOT NULL)");
+		appendStringInfoString(buf, " IS NOT NULL)");
 }
 
 /*
@@ -1187,11 +1362,11 @@ deparseArrayExpr(StringInfo buf, ArrayExpr *node, PlannerInfo *root)
 	bool		first = true;
 	ListCell   *lc;
 
-	appendStringInfo(buf, "ARRAY[");
+	appendStringInfoString(buf, "ARRAY[");
 	foreach(lc, node->elements)
 	{
 		if (!first)
-			appendStringInfo(buf, ", ");
+			appendStringInfoString(buf, ", ");
 		deparseExpr(buf, lfirst(lc), root);
 		first = false;
 	}
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 07c7970dc7b256f92b34783d5c69e79de6d77020..21429ca16610ff902d62fc5cfd7719da70fa58d0 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -731,3 +731,1512 @@ SELECT * FROM ft1 ORDER BY c1 LIMIT 1;
 (1 row)
 
 COMMIT;
+-- ===================================================================
+-- test writable foreign table stuff
+-- ===================================================================
+EXPLAIN (verbose, costs off)
+INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20;
+                                                                                                            QUERY PLAN                                                                                                             
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Insert on public.ft2
+   Remote SQL: INSERT INTO "S 1"."T 1"("C 1", c2, c3) VALUES ($1, $2, $3)
+   ->  Subquery Scan on "*SELECT*"
+         Output: NULL::integer, "*SELECT*"."?column?", "*SELECT*"."?column?_1", "*SELECT*"."?column?_2", NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying, NULL::bpchar, NULL::user_enum
+         ->  Limit
+               Output: ((ft2_1.c1 + 1000)), ((ft2_1.c2 + 100)), ((ft2_1.c3 || ft2_1.c3))
+               ->  Foreign Scan on public.ft2 ft2_1
+                     Output: (ft2_1.c1 + 1000), (ft2_1.c2 + 100), (ft2_1.c3 || ft2_1.c3)
+                     Remote SQL: SELECT "C 1", c2, c3, NULL, NULL, NULL, NULL, NULL FROM "S 1"."T 1"
+(9 rows)
+
+INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20;
+INSERT INTO ft2 (c1,c2,c3)
+  VALUES (1101,201,'aaa'), (1102,202,'bbb'), (1103,203,'ccc') RETURNING *;
+  c1  | c2  | c3  | c4 | c5 | c6 | c7 | c8 
+------+-----+-----+----+----+----+----+----
+ 1101 | 201 | aaa |    |    |    |    | 
+ 1102 | 202 | bbb |    |    |    |    | 
+ 1103 | 203 | ccc |    |    |    |    | 
+(3 rows)
+
+INSERT INTO ft2 (c1,c2,c3) VALUES (1104,204,'ddd'), (1105,205,'eee');
+UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3;
+UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *;
+  c1  | c2  |         c3         |              c4              |            c5            | c6 |     c7     | c8  
+------+-----+--------------------+------------------------------+--------------------------+----+------------+-----
+    7 | 407 | 00007_update7      | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7  | 7          | foo
+   17 | 407 | 00017_update7      | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7  | 7          | foo
+   27 | 407 | 00027_update7      | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7  | 7          | foo
+   37 | 407 | 00037_update7      | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7  | 7          | foo
+   47 | 407 | 00047_update7      | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7  | 7          | foo
+   57 | 407 | 00057_update7      | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7  | 7          | foo
+   67 | 407 | 00067_update7      | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7  | 7          | foo
+   77 | 407 | 00077_update7      | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7  | 7          | foo
+   87 | 407 | 00087_update7      | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7  | 7          | foo
+   97 | 407 | 00097_update7      | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7  | 7          | foo
+  107 | 407 | 00107_update7      | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7  | 7          | foo
+  117 | 407 | 00117_update7      | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7  | 7          | foo
+  127 | 407 | 00127_update7      | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7  | 7          | foo
+  137 | 407 | 00137_update7      | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7  | 7          | foo
+  147 | 407 | 00147_update7      | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7  | 7          | foo
+  157 | 407 | 00157_update7      | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7  | 7          | foo
+  167 | 407 | 00167_update7      | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7  | 7          | foo
+  177 | 407 | 00177_update7      | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7  | 7          | foo
+  187 | 407 | 00187_update7      | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7  | 7          | foo
+  197 | 407 | 00197_update7      | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7  | 7          | foo
+  207 | 407 | 00207_update7      | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7  | 7          | foo
+  217 | 407 | 00217_update7      | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7  | 7          | foo
+  227 | 407 | 00227_update7      | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7  | 7          | foo
+  237 | 407 | 00237_update7      | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7  | 7          | foo
+  247 | 407 | 00247_update7      | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7  | 7          | foo
+  257 | 407 | 00257_update7      | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7  | 7          | foo
+  267 | 407 | 00267_update7      | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7  | 7          | foo
+  277 | 407 | 00277_update7      | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7  | 7          | foo
+  287 | 407 | 00287_update7      | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7  | 7          | foo
+  297 | 407 | 00297_update7      | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7  | 7          | foo
+  307 | 407 | 00307_update7      | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7  | 7          | foo
+  317 | 407 | 00317_update7      | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7  | 7          | foo
+  327 | 407 | 00327_update7      | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7  | 7          | foo
+  337 | 407 | 00337_update7      | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7  | 7          | foo
+  347 | 407 | 00347_update7      | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7  | 7          | foo
+  357 | 407 | 00357_update7      | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7  | 7          | foo
+  367 | 407 | 00367_update7      | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7  | 7          | foo
+  377 | 407 | 00377_update7      | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7  | 7          | foo
+  387 | 407 | 00387_update7      | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7  | 7          | foo
+  397 | 407 | 00397_update7      | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7  | 7          | foo
+  407 | 407 | 00407_update7      | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7  | 7          | foo
+  417 | 407 | 00417_update7      | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7  | 7          | foo
+  427 | 407 | 00427_update7      | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7  | 7          | foo
+  437 | 407 | 00437_update7      | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7  | 7          | foo
+  447 | 407 | 00447_update7      | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7  | 7          | foo
+  457 | 407 | 00457_update7      | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7  | 7          | foo
+  467 | 407 | 00467_update7      | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7  | 7          | foo
+  477 | 407 | 00477_update7      | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7  | 7          | foo
+  487 | 407 | 00487_update7      | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7  | 7          | foo
+  497 | 407 | 00497_update7      | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7  | 7          | foo
+  507 | 407 | 00507_update7      | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7  | 7          | foo
+  517 | 407 | 00517_update7      | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7  | 7          | foo
+  527 | 407 | 00527_update7      | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7  | 7          | foo
+  537 | 407 | 00537_update7      | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7  | 7          | foo
+  547 | 407 | 00547_update7      | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7  | 7          | foo
+  557 | 407 | 00557_update7      | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7  | 7          | foo
+  567 | 407 | 00567_update7      | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7  | 7          | foo
+  577 | 407 | 00577_update7      | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7  | 7          | foo
+  587 | 407 | 00587_update7      | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7  | 7          | foo
+  597 | 407 | 00597_update7      | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7  | 7          | foo
+  607 | 407 | 00607_update7      | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7  | 7          | foo
+  617 | 407 | 00617_update7      | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7  | 7          | foo
+  627 | 407 | 00627_update7      | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7  | 7          | foo
+  637 | 407 | 00637_update7      | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7  | 7          | foo
+  647 | 407 | 00647_update7      | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7  | 7          | foo
+  657 | 407 | 00657_update7      | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7  | 7          | foo
+  667 | 407 | 00667_update7      | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7  | 7          | foo
+  677 | 407 | 00677_update7      | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7  | 7          | foo
+  687 | 407 | 00687_update7      | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7  | 7          | foo
+  697 | 407 | 00697_update7      | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7  | 7          | foo
+  707 | 407 | 00707_update7      | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7  | 7          | foo
+  717 | 407 | 00717_update7      | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7  | 7          | foo
+  727 | 407 | 00727_update7      | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7  | 7          | foo
+  737 | 407 | 00737_update7      | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7  | 7          | foo
+  747 | 407 | 00747_update7      | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7  | 7          | foo
+  757 | 407 | 00757_update7      | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7  | 7          | foo
+  767 | 407 | 00767_update7      | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7  | 7          | foo
+  777 | 407 | 00777_update7      | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7  | 7          | foo
+  787 | 407 | 00787_update7      | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7  | 7          | foo
+  797 | 407 | 00797_update7      | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7  | 7          | foo
+  807 | 407 | 00807_update7      | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7  | 7          | foo
+  817 | 407 | 00817_update7      | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7  | 7          | foo
+  827 | 407 | 00827_update7      | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7  | 7          | foo
+  837 | 407 | 00837_update7      | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7  | 7          | foo
+  847 | 407 | 00847_update7      | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7  | 7          | foo
+  857 | 407 | 00857_update7      | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7  | 7          | foo
+  867 | 407 | 00867_update7      | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7  | 7          | foo
+  877 | 407 | 00877_update7      | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7  | 7          | foo
+  887 | 407 | 00887_update7      | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7  | 7          | foo
+  897 | 407 | 00897_update7      | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7  | 7          | foo
+  907 | 407 | 00907_update7      | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7  | 7          | foo
+  917 | 407 | 00917_update7      | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7  | 7          | foo
+  927 | 407 | 00927_update7      | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7  | 7          | foo
+  937 | 407 | 00937_update7      | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7  | 7          | foo
+  947 | 407 | 00947_update7      | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7  | 7          | foo
+  957 | 407 | 00957_update7      | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7  | 7          | foo
+  967 | 407 | 00967_update7      | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7  | 7          | foo
+  977 | 407 | 00977_update7      | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7  | 7          | foo
+  987 | 407 | 00987_update7      | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7  | 7          | foo
+  997 | 407 | 00997_update7      | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7  | 7          | foo
+ 1007 | 507 | 0000700007_update7 |                              |                          |    |            | 
+ 1017 | 507 | 0001700017_update7 |                              |                          |    |            | 
+(102 rows)
+
+EXPLAIN (verbose, costs off)
+UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9'
+  FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9;
+                                                                  QUERY PLAN                                                                  
+----------------------------------------------------------------------------------------------------------------------------------------------
+ Update on public.ft2
+   Remote SQL: UPDATE "S 1"."T 1" SET c2 = $2, c3 = $3 WHERE ctid = $1
+   ->  Hash Join
+         Output: NULL::integer, ft2.c1, (ft2.c2 + 500), (ft2.c3 || '_update9'::text), ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid, ft1.*
+         Hash Cond: (ft2.c2 = ft1.c1)
+         ->  Foreign Scan on public.ft2
+               Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid
+               Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" FOR UPDATE
+         ->  Hash
+               Output: ft1.*, ft1.c1
+               ->  Foreign Scan on public.ft1
+                     Output: ft1.*, ft1.c1
+                     Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 9))
+(13 rows)
+
+UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9'
+  FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9;
+DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING *;
+  c1  | c2  |     c3     |              c4              |            c5            | c6 |     c7     | c8  
+------+-----+------------+------------------------------+--------------------------+----+------------+-----
+    5 |   5 | 00005      | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5  | 5          | foo
+   15 |   5 | 00015      | Fri Jan 16 00:00:00 1970 PST | Fri Jan 16 00:00:00 1970 | 5  | 5          | foo
+   25 |   5 | 00025      | Mon Jan 26 00:00:00 1970 PST | Mon Jan 26 00:00:00 1970 | 5  | 5          | foo
+   35 |   5 | 00035      | Thu Feb 05 00:00:00 1970 PST | Thu Feb 05 00:00:00 1970 | 5  | 5          | foo
+   45 |   5 | 00045      | Sun Feb 15 00:00:00 1970 PST | Sun Feb 15 00:00:00 1970 | 5  | 5          | foo
+   55 |   5 | 00055      | Wed Feb 25 00:00:00 1970 PST | Wed Feb 25 00:00:00 1970 | 5  | 5          | foo
+   65 |   5 | 00065      | Sat Mar 07 00:00:00 1970 PST | Sat Mar 07 00:00:00 1970 | 5  | 5          | foo
+   75 |   5 | 00075      | Tue Mar 17 00:00:00 1970 PST | Tue Mar 17 00:00:00 1970 | 5  | 5          | foo
+   85 |   5 | 00085      | Fri Mar 27 00:00:00 1970 PST | Fri Mar 27 00:00:00 1970 | 5  | 5          | foo
+   95 |   5 | 00095      | Mon Apr 06 00:00:00 1970 PST | Mon Apr 06 00:00:00 1970 | 5  | 5          | foo
+  105 |   5 | 00105      | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5  | 5          | foo
+  115 |   5 | 00115      | Fri Jan 16 00:00:00 1970 PST | Fri Jan 16 00:00:00 1970 | 5  | 5          | foo
+  125 |   5 | 00125      | Mon Jan 26 00:00:00 1970 PST | Mon Jan 26 00:00:00 1970 | 5  | 5          | foo
+  135 |   5 | 00135      | Thu Feb 05 00:00:00 1970 PST | Thu Feb 05 00:00:00 1970 | 5  | 5          | foo
+  145 |   5 | 00145      | Sun Feb 15 00:00:00 1970 PST | Sun Feb 15 00:00:00 1970 | 5  | 5          | foo
+  155 |   5 | 00155      | Wed Feb 25 00:00:00 1970 PST | Wed Feb 25 00:00:00 1970 | 5  | 5          | foo
+  165 |   5 | 00165      | Sat Mar 07 00:00:00 1970 PST | Sat Mar 07 00:00:00 1970 | 5  | 5          | foo
+  175 |   5 | 00175      | Tue Mar 17 00:00:00 1970 PST | Tue Mar 17 00:00:00 1970 | 5  | 5          | foo
+  185 |   5 | 00185      | Fri Mar 27 00:00:00 1970 PST | Fri Mar 27 00:00:00 1970 | 5  | 5          | foo
+  195 |   5 | 00195      | Mon Apr 06 00:00:00 1970 PST | Mon Apr 06 00:00:00 1970 | 5  | 5          | foo
+  205 |   5 | 00205      | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5  | 5          | foo
+  215 |   5 | 00215      | Fri Jan 16 00:00:00 1970 PST | Fri Jan 16 00:00:00 1970 | 5  | 5          | foo
+  225 |   5 | 00225      | Mon Jan 26 00:00:00 1970 PST | Mon Jan 26 00:00:00 1970 | 5  | 5          | foo
+  235 |   5 | 00235      | Thu Feb 05 00:00:00 1970 PST | Thu Feb 05 00:00:00 1970 | 5  | 5          | foo
+  245 |   5 | 00245      | Sun Feb 15 00:00:00 1970 PST | Sun Feb 15 00:00:00 1970 | 5  | 5          | foo
+  255 |   5 | 00255      | Wed Feb 25 00:00:00 1970 PST | Wed Feb 25 00:00:00 1970 | 5  | 5          | foo
+  265 |   5 | 00265      | Sat Mar 07 00:00:00 1970 PST | Sat Mar 07 00:00:00 1970 | 5  | 5          | foo
+  275 |   5 | 00275      | Tue Mar 17 00:00:00 1970 PST | Tue Mar 17 00:00:00 1970 | 5  | 5          | foo
+  285 |   5 | 00285      | Fri Mar 27 00:00:00 1970 PST | Fri Mar 27 00:00:00 1970 | 5  | 5          | foo
+  295 |   5 | 00295      | Mon Apr 06 00:00:00 1970 PST | Mon Apr 06 00:00:00 1970 | 5  | 5          | foo
+  305 |   5 | 00305      | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5  | 5          | foo
+  315 |   5 | 00315      | Fri Jan 16 00:00:00 1970 PST | Fri Jan 16 00:00:00 1970 | 5  | 5          | foo
+  325 |   5 | 00325      | Mon Jan 26 00:00:00 1970 PST | Mon Jan 26 00:00:00 1970 | 5  | 5          | foo
+  335 |   5 | 00335      | Thu Feb 05 00:00:00 1970 PST | Thu Feb 05 00:00:00 1970 | 5  | 5          | foo
+  345 |   5 | 00345      | Sun Feb 15 00:00:00 1970 PST | Sun Feb 15 00:00:00 1970 | 5  | 5          | foo
+  355 |   5 | 00355      | Wed Feb 25 00:00:00 1970 PST | Wed Feb 25 00:00:00 1970 | 5  | 5          | foo
+  365 |   5 | 00365      | Sat Mar 07 00:00:00 1970 PST | Sat Mar 07 00:00:00 1970 | 5  | 5          | foo
+  375 |   5 | 00375      | Tue Mar 17 00:00:00 1970 PST | Tue Mar 17 00:00:00 1970 | 5  | 5          | foo
+  385 |   5 | 00385      | Fri Mar 27 00:00:00 1970 PST | Fri Mar 27 00:00:00 1970 | 5  | 5          | foo
+  395 |   5 | 00395      | Mon Apr 06 00:00:00 1970 PST | Mon Apr 06 00:00:00 1970 | 5  | 5          | foo
+  405 |   5 | 00405      | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5  | 5          | foo
+  415 |   5 | 00415      | Fri Jan 16 00:00:00 1970 PST | Fri Jan 16 00:00:00 1970 | 5  | 5          | foo
+  425 |   5 | 00425      | Mon Jan 26 00:00:00 1970 PST | Mon Jan 26 00:00:00 1970 | 5  | 5          | foo
+  435 |   5 | 00435      | Thu Feb 05 00:00:00 1970 PST | Thu Feb 05 00:00:00 1970 | 5  | 5          | foo
+  445 |   5 | 00445      | Sun Feb 15 00:00:00 1970 PST | Sun Feb 15 00:00:00 1970 | 5  | 5          | foo
+  455 |   5 | 00455      | Wed Feb 25 00:00:00 1970 PST | Wed Feb 25 00:00:00 1970 | 5  | 5          | foo
+  465 |   5 | 00465      | Sat Mar 07 00:00:00 1970 PST | Sat Mar 07 00:00:00 1970 | 5  | 5          | foo
+  475 |   5 | 00475      | Tue Mar 17 00:00:00 1970 PST | Tue Mar 17 00:00:00 1970 | 5  | 5          | foo
+  485 |   5 | 00485      | Fri Mar 27 00:00:00 1970 PST | Fri Mar 27 00:00:00 1970 | 5  | 5          | foo
+  495 |   5 | 00495      | Mon Apr 06 00:00:00 1970 PST | Mon Apr 06 00:00:00 1970 | 5  | 5          | foo
+  505 |   5 | 00505      | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5  | 5          | foo
+  515 |   5 | 00515      | Fri Jan 16 00:00:00 1970 PST | Fri Jan 16 00:00:00 1970 | 5  | 5          | foo
+  525 |   5 | 00525      | Mon Jan 26 00:00:00 1970 PST | Mon Jan 26 00:00:00 1970 | 5  | 5          | foo
+  535 |   5 | 00535      | Thu Feb 05 00:00:00 1970 PST | Thu Feb 05 00:00:00 1970 | 5  | 5          | foo
+  545 |   5 | 00545      | Sun Feb 15 00:00:00 1970 PST | Sun Feb 15 00:00:00 1970 | 5  | 5          | foo
+  555 |   5 | 00555      | Wed Feb 25 00:00:00 1970 PST | Wed Feb 25 00:00:00 1970 | 5  | 5          | foo
+  565 |   5 | 00565      | Sat Mar 07 00:00:00 1970 PST | Sat Mar 07 00:00:00 1970 | 5  | 5          | foo
+  575 |   5 | 00575      | Tue Mar 17 00:00:00 1970 PST | Tue Mar 17 00:00:00 1970 | 5  | 5          | foo
+  585 |   5 | 00585      | Fri Mar 27 00:00:00 1970 PST | Fri Mar 27 00:00:00 1970 | 5  | 5          | foo
+  595 |   5 | 00595      | Mon Apr 06 00:00:00 1970 PST | Mon Apr 06 00:00:00 1970 | 5  | 5          | foo
+  605 |   5 | 00605      | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5  | 5          | foo
+  615 |   5 | 00615      | Fri Jan 16 00:00:00 1970 PST | Fri Jan 16 00:00:00 1970 | 5  | 5          | foo
+  625 |   5 | 00625      | Mon Jan 26 00:00:00 1970 PST | Mon Jan 26 00:00:00 1970 | 5  | 5          | foo
+  635 |   5 | 00635      | Thu Feb 05 00:00:00 1970 PST | Thu Feb 05 00:00:00 1970 | 5  | 5          | foo
+  645 |   5 | 00645      | Sun Feb 15 00:00:00 1970 PST | Sun Feb 15 00:00:00 1970 | 5  | 5          | foo
+  655 |   5 | 00655      | Wed Feb 25 00:00:00 1970 PST | Wed Feb 25 00:00:00 1970 | 5  | 5          | foo
+  665 |   5 | 00665      | Sat Mar 07 00:00:00 1970 PST | Sat Mar 07 00:00:00 1970 | 5  | 5          | foo
+  675 |   5 | 00675      | Tue Mar 17 00:00:00 1970 PST | Tue Mar 17 00:00:00 1970 | 5  | 5          | foo
+  685 |   5 | 00685      | Fri Mar 27 00:00:00 1970 PST | Fri Mar 27 00:00:00 1970 | 5  | 5          | foo
+  695 |   5 | 00695      | Mon Apr 06 00:00:00 1970 PST | Mon Apr 06 00:00:00 1970 | 5  | 5          | foo
+  705 |   5 | 00705      | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5  | 5          | foo
+  715 |   5 | 00715      | Fri Jan 16 00:00:00 1970 PST | Fri Jan 16 00:00:00 1970 | 5  | 5          | foo
+  725 |   5 | 00725      | Mon Jan 26 00:00:00 1970 PST | Mon Jan 26 00:00:00 1970 | 5  | 5          | foo
+  735 |   5 | 00735      | Thu Feb 05 00:00:00 1970 PST | Thu Feb 05 00:00:00 1970 | 5  | 5          | foo
+  745 |   5 | 00745      | Sun Feb 15 00:00:00 1970 PST | Sun Feb 15 00:00:00 1970 | 5  | 5          | foo
+  755 |   5 | 00755      | Wed Feb 25 00:00:00 1970 PST | Wed Feb 25 00:00:00 1970 | 5  | 5          | foo
+  765 |   5 | 00765      | Sat Mar 07 00:00:00 1970 PST | Sat Mar 07 00:00:00 1970 | 5  | 5          | foo
+  775 |   5 | 00775      | Tue Mar 17 00:00:00 1970 PST | Tue Mar 17 00:00:00 1970 | 5  | 5          | foo
+  785 |   5 | 00785      | Fri Mar 27 00:00:00 1970 PST | Fri Mar 27 00:00:00 1970 | 5  | 5          | foo
+  795 |   5 | 00795      | Mon Apr 06 00:00:00 1970 PST | Mon Apr 06 00:00:00 1970 | 5  | 5          | foo
+  805 |   5 | 00805      | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5  | 5          | foo
+  815 |   5 | 00815      | Fri Jan 16 00:00:00 1970 PST | Fri Jan 16 00:00:00 1970 | 5  | 5          | foo
+  825 |   5 | 00825      | Mon Jan 26 00:00:00 1970 PST | Mon Jan 26 00:00:00 1970 | 5  | 5          | foo
+  835 |   5 | 00835      | Thu Feb 05 00:00:00 1970 PST | Thu Feb 05 00:00:00 1970 | 5  | 5          | foo
+  845 |   5 | 00845      | Sun Feb 15 00:00:00 1970 PST | Sun Feb 15 00:00:00 1970 | 5  | 5          | foo
+  855 |   5 | 00855      | Wed Feb 25 00:00:00 1970 PST | Wed Feb 25 00:00:00 1970 | 5  | 5          | foo
+  865 |   5 | 00865      | Sat Mar 07 00:00:00 1970 PST | Sat Mar 07 00:00:00 1970 | 5  | 5          | foo
+  875 |   5 | 00875      | Tue Mar 17 00:00:00 1970 PST | Tue Mar 17 00:00:00 1970 | 5  | 5          | foo
+  885 |   5 | 00885      | Fri Mar 27 00:00:00 1970 PST | Fri Mar 27 00:00:00 1970 | 5  | 5          | foo
+  895 |   5 | 00895      | Mon Apr 06 00:00:00 1970 PST | Mon Apr 06 00:00:00 1970 | 5  | 5          | foo
+  905 |   5 | 00905      | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5  | 5          | foo
+  915 |   5 | 00915      | Fri Jan 16 00:00:00 1970 PST | Fri Jan 16 00:00:00 1970 | 5  | 5          | foo
+  925 |   5 | 00925      | Mon Jan 26 00:00:00 1970 PST | Mon Jan 26 00:00:00 1970 | 5  | 5          | foo
+  935 |   5 | 00935      | Thu Feb 05 00:00:00 1970 PST | Thu Feb 05 00:00:00 1970 | 5  | 5          | foo
+  945 |   5 | 00945      | Sun Feb 15 00:00:00 1970 PST | Sun Feb 15 00:00:00 1970 | 5  | 5          | foo
+  955 |   5 | 00955      | Wed Feb 25 00:00:00 1970 PST | Wed Feb 25 00:00:00 1970 | 5  | 5          | foo
+  965 |   5 | 00965      | Sat Mar 07 00:00:00 1970 PST | Sat Mar 07 00:00:00 1970 | 5  | 5          | foo
+  975 |   5 | 00975      | Tue Mar 17 00:00:00 1970 PST | Tue Mar 17 00:00:00 1970 | 5  | 5          | foo
+  985 |   5 | 00985      | Fri Mar 27 00:00:00 1970 PST | Fri Mar 27 00:00:00 1970 | 5  | 5          | foo
+  995 |   5 | 00995      | Mon Apr 06 00:00:00 1970 PST | Mon Apr 06 00:00:00 1970 | 5  | 5          | foo
+ 1005 | 105 | 0000500005 |                              |                          |    |            | 
+ 1015 | 105 | 0001500015 |                              |                          |    |            | 
+ 1105 | 205 | eee        |                              |                          |    |            | 
+(103 rows)
+
+EXPLAIN (verbose, costs off)
+DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2;
+                                                      QUERY PLAN                                                      
+----------------------------------------------------------------------------------------------------------------------
+ Delete on public.ft2
+   Remote SQL: DELETE FROM "S 1"."T 1" WHERE ctid = $1
+   ->  Hash Join
+         Output: ft2.ctid, ft1.*
+         Hash Cond: (ft2.c2 = ft1.c1)
+         ->  Foreign Scan on public.ft2
+               Output: ft2.ctid, ft2.c2
+               Remote SQL: SELECT NULL, c2, NULL, NULL, NULL, NULL, NULL, NULL, ctid FROM "S 1"."T 1" FOR UPDATE
+         ->  Hash
+               Output: ft1.*, ft1.c1
+               ->  Foreign Scan on public.ft1
+                     Output: ft1.*, ft1.c1
+                     Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 2))
+(13 rows)
+
+DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2;
+SELECT c1,c2,c3,c4 FROM ft2 ORDER BY c1;
+  c1  | c2  |         c3         |              c4              
+------+-----+--------------------+------------------------------
+    1 |   1 | 00001              | Fri Jan 02 00:00:00 1970 PST
+    3 | 303 | 00003_update3      | Sun Jan 04 00:00:00 1970 PST
+    4 |   4 | 00004              | Mon Jan 05 00:00:00 1970 PST
+    6 |   6 | 00006              | Wed Jan 07 00:00:00 1970 PST
+    7 | 407 | 00007_update7      | Thu Jan 08 00:00:00 1970 PST
+    8 |   8 | 00008              | Fri Jan 09 00:00:00 1970 PST
+    9 | 509 | 00009_update9      | Sat Jan 10 00:00:00 1970 PST
+   10 |   0 | 00010              | Sun Jan 11 00:00:00 1970 PST
+   11 |   1 | 00011              | Mon Jan 12 00:00:00 1970 PST
+   13 | 303 | 00013_update3      | Wed Jan 14 00:00:00 1970 PST
+   14 |   4 | 00014              | Thu Jan 15 00:00:00 1970 PST
+   16 |   6 | 00016              | Sat Jan 17 00:00:00 1970 PST
+   17 | 407 | 00017_update7      | Sun Jan 18 00:00:00 1970 PST
+   18 |   8 | 00018              | Mon Jan 19 00:00:00 1970 PST
+   19 | 509 | 00019_update9      | Tue Jan 20 00:00:00 1970 PST
+   20 |   0 | 00020              | Wed Jan 21 00:00:00 1970 PST
+   21 |   1 | 00021              | Thu Jan 22 00:00:00 1970 PST
+   23 | 303 | 00023_update3      | Sat Jan 24 00:00:00 1970 PST
+   24 |   4 | 00024              | Sun Jan 25 00:00:00 1970 PST
+   26 |   6 | 00026              | Tue Jan 27 00:00:00 1970 PST
+   27 | 407 | 00027_update7      | Wed Jan 28 00:00:00 1970 PST
+   28 |   8 | 00028              | Thu Jan 29 00:00:00 1970 PST
+   29 | 509 | 00029_update9      | Fri Jan 30 00:00:00 1970 PST
+   30 |   0 | 00030              | Sat Jan 31 00:00:00 1970 PST
+   31 |   1 | 00031              | Sun Feb 01 00:00:00 1970 PST
+   33 | 303 | 00033_update3      | Tue Feb 03 00:00:00 1970 PST
+   34 |   4 | 00034              | Wed Feb 04 00:00:00 1970 PST
+   36 |   6 | 00036              | Fri Feb 06 00:00:00 1970 PST
+   37 | 407 | 00037_update7      | Sat Feb 07 00:00:00 1970 PST
+   38 |   8 | 00038              | Sun Feb 08 00:00:00 1970 PST
+   39 | 509 | 00039_update9      | Mon Feb 09 00:00:00 1970 PST
+   40 |   0 | 00040              | Tue Feb 10 00:00:00 1970 PST
+   41 |   1 | 00041              | Wed Feb 11 00:00:00 1970 PST
+   43 | 303 | 00043_update3      | Fri Feb 13 00:00:00 1970 PST
+   44 |   4 | 00044              | Sat Feb 14 00:00:00 1970 PST
+   46 |   6 | 00046              | Mon Feb 16 00:00:00 1970 PST
+   47 | 407 | 00047_update7      | Tue Feb 17 00:00:00 1970 PST
+   48 |   8 | 00048              | Wed Feb 18 00:00:00 1970 PST
+   49 | 509 | 00049_update9      | Thu Feb 19 00:00:00 1970 PST
+   50 |   0 | 00050              | Fri Feb 20 00:00:00 1970 PST
+   51 |   1 | 00051              | Sat Feb 21 00:00:00 1970 PST
+   53 | 303 | 00053_update3      | Mon Feb 23 00:00:00 1970 PST
+   54 |   4 | 00054              | Tue Feb 24 00:00:00 1970 PST
+   56 |   6 | 00056              | Thu Feb 26 00:00:00 1970 PST
+   57 | 407 | 00057_update7      | Fri Feb 27 00:00:00 1970 PST
+   58 |   8 | 00058              | Sat Feb 28 00:00:00 1970 PST
+   59 | 509 | 00059_update9      | Sun Mar 01 00:00:00 1970 PST
+   60 |   0 | 00060              | Mon Mar 02 00:00:00 1970 PST
+   61 |   1 | 00061              | Tue Mar 03 00:00:00 1970 PST
+   63 | 303 | 00063_update3      | Thu Mar 05 00:00:00 1970 PST
+   64 |   4 | 00064              | Fri Mar 06 00:00:00 1970 PST
+   66 |   6 | 00066              | Sun Mar 08 00:00:00 1970 PST
+   67 | 407 | 00067_update7      | Mon Mar 09 00:00:00 1970 PST
+   68 |   8 | 00068              | Tue Mar 10 00:00:00 1970 PST
+   69 | 509 | 00069_update9      | Wed Mar 11 00:00:00 1970 PST
+   70 |   0 | 00070              | Thu Mar 12 00:00:00 1970 PST
+   71 |   1 | 00071              | Fri Mar 13 00:00:00 1970 PST
+   73 | 303 | 00073_update3      | Sun Mar 15 00:00:00 1970 PST
+   74 |   4 | 00074              | Mon Mar 16 00:00:00 1970 PST
+   76 |   6 | 00076              | Wed Mar 18 00:00:00 1970 PST
+   77 | 407 | 00077_update7      | Thu Mar 19 00:00:00 1970 PST
+   78 |   8 | 00078              | Fri Mar 20 00:00:00 1970 PST
+   79 | 509 | 00079_update9      | Sat Mar 21 00:00:00 1970 PST
+   80 |   0 | 00080              | Sun Mar 22 00:00:00 1970 PST
+   81 |   1 | 00081              | Mon Mar 23 00:00:00 1970 PST
+   83 | 303 | 00083_update3      | Wed Mar 25 00:00:00 1970 PST
+   84 |   4 | 00084              | Thu Mar 26 00:00:00 1970 PST
+   86 |   6 | 00086              | Sat Mar 28 00:00:00 1970 PST
+   87 | 407 | 00087_update7      | Sun Mar 29 00:00:00 1970 PST
+   88 |   8 | 00088              | Mon Mar 30 00:00:00 1970 PST
+   89 | 509 | 00089_update9      | Tue Mar 31 00:00:00 1970 PST
+   90 |   0 | 00090              | Wed Apr 01 00:00:00 1970 PST
+   91 |   1 | 00091              | Thu Apr 02 00:00:00 1970 PST
+   93 | 303 | 00093_update3      | Sat Apr 04 00:00:00 1970 PST
+   94 |   4 | 00094              | Sun Apr 05 00:00:00 1970 PST
+   96 |   6 | 00096              | Tue Apr 07 00:00:00 1970 PST
+   97 | 407 | 00097_update7      | Wed Apr 08 00:00:00 1970 PST
+   98 |   8 | 00098              | Thu Apr 09 00:00:00 1970 PST
+   99 | 509 | 00099_update9      | Fri Apr 10 00:00:00 1970 PST
+  100 |   0 | 00100              | Thu Jan 01 00:00:00 1970 PST
+  101 |   1 | 00101              | Fri Jan 02 00:00:00 1970 PST
+  103 | 303 | 00103_update3      | Sun Jan 04 00:00:00 1970 PST
+  104 |   4 | 00104              | Mon Jan 05 00:00:00 1970 PST
+  106 |   6 | 00106              | Wed Jan 07 00:00:00 1970 PST
+  107 | 407 | 00107_update7      | Thu Jan 08 00:00:00 1970 PST
+  108 |   8 | 00108              | Fri Jan 09 00:00:00 1970 PST
+  109 | 509 | 00109_update9      | Sat Jan 10 00:00:00 1970 PST
+  110 |   0 | 00110              | Sun Jan 11 00:00:00 1970 PST
+  111 |   1 | 00111              | Mon Jan 12 00:00:00 1970 PST
+  113 | 303 | 00113_update3      | Wed Jan 14 00:00:00 1970 PST
+  114 |   4 | 00114              | Thu Jan 15 00:00:00 1970 PST
+  116 |   6 | 00116              | Sat Jan 17 00:00:00 1970 PST
+  117 | 407 | 00117_update7      | Sun Jan 18 00:00:00 1970 PST
+  118 |   8 | 00118              | Mon Jan 19 00:00:00 1970 PST
+  119 | 509 | 00119_update9      | Tue Jan 20 00:00:00 1970 PST
+  120 |   0 | 00120              | Wed Jan 21 00:00:00 1970 PST
+  121 |   1 | 00121              | Thu Jan 22 00:00:00 1970 PST
+  123 | 303 | 00123_update3      | Sat Jan 24 00:00:00 1970 PST
+  124 |   4 | 00124              | Sun Jan 25 00:00:00 1970 PST
+  126 |   6 | 00126              | Tue Jan 27 00:00:00 1970 PST
+  127 | 407 | 00127_update7      | Wed Jan 28 00:00:00 1970 PST
+  128 |   8 | 00128              | Thu Jan 29 00:00:00 1970 PST
+  129 | 509 | 00129_update9      | Fri Jan 30 00:00:00 1970 PST
+  130 |   0 | 00130              | Sat Jan 31 00:00:00 1970 PST
+  131 |   1 | 00131              | Sun Feb 01 00:00:00 1970 PST
+  133 | 303 | 00133_update3      | Tue Feb 03 00:00:00 1970 PST
+  134 |   4 | 00134              | Wed Feb 04 00:00:00 1970 PST
+  136 |   6 | 00136              | Fri Feb 06 00:00:00 1970 PST
+  137 | 407 | 00137_update7      | Sat Feb 07 00:00:00 1970 PST
+  138 |   8 | 00138              | Sun Feb 08 00:00:00 1970 PST
+  139 | 509 | 00139_update9      | Mon Feb 09 00:00:00 1970 PST
+  140 |   0 | 00140              | Tue Feb 10 00:00:00 1970 PST
+  141 |   1 | 00141              | Wed Feb 11 00:00:00 1970 PST
+  143 | 303 | 00143_update3      | Fri Feb 13 00:00:00 1970 PST
+  144 |   4 | 00144              | Sat Feb 14 00:00:00 1970 PST
+  146 |   6 | 00146              | Mon Feb 16 00:00:00 1970 PST
+  147 | 407 | 00147_update7      | Tue Feb 17 00:00:00 1970 PST
+  148 |   8 | 00148              | Wed Feb 18 00:00:00 1970 PST
+  149 | 509 | 00149_update9      | Thu Feb 19 00:00:00 1970 PST
+  150 |   0 | 00150              | Fri Feb 20 00:00:00 1970 PST
+  151 |   1 | 00151              | Sat Feb 21 00:00:00 1970 PST
+  153 | 303 | 00153_update3      | Mon Feb 23 00:00:00 1970 PST
+  154 |   4 | 00154              | Tue Feb 24 00:00:00 1970 PST
+  156 |   6 | 00156              | Thu Feb 26 00:00:00 1970 PST
+  157 | 407 | 00157_update7      | Fri Feb 27 00:00:00 1970 PST
+  158 |   8 | 00158              | Sat Feb 28 00:00:00 1970 PST
+  159 | 509 | 00159_update9      | Sun Mar 01 00:00:00 1970 PST
+  160 |   0 | 00160              | Mon Mar 02 00:00:00 1970 PST
+  161 |   1 | 00161              | Tue Mar 03 00:00:00 1970 PST
+  163 | 303 | 00163_update3      | Thu Mar 05 00:00:00 1970 PST
+  164 |   4 | 00164              | Fri Mar 06 00:00:00 1970 PST
+  166 |   6 | 00166              | Sun Mar 08 00:00:00 1970 PST
+  167 | 407 | 00167_update7      | Mon Mar 09 00:00:00 1970 PST
+  168 |   8 | 00168              | Tue Mar 10 00:00:00 1970 PST
+  169 | 509 | 00169_update9      | Wed Mar 11 00:00:00 1970 PST
+  170 |   0 | 00170              | Thu Mar 12 00:00:00 1970 PST
+  171 |   1 | 00171              | Fri Mar 13 00:00:00 1970 PST
+  173 | 303 | 00173_update3      | Sun Mar 15 00:00:00 1970 PST
+  174 |   4 | 00174              | Mon Mar 16 00:00:00 1970 PST
+  176 |   6 | 00176              | Wed Mar 18 00:00:00 1970 PST
+  177 | 407 | 00177_update7      | Thu Mar 19 00:00:00 1970 PST
+  178 |   8 | 00178              | Fri Mar 20 00:00:00 1970 PST
+  179 | 509 | 00179_update9      | Sat Mar 21 00:00:00 1970 PST
+  180 |   0 | 00180              | Sun Mar 22 00:00:00 1970 PST
+  181 |   1 | 00181              | Mon Mar 23 00:00:00 1970 PST
+  183 | 303 | 00183_update3      | Wed Mar 25 00:00:00 1970 PST
+  184 |   4 | 00184              | Thu Mar 26 00:00:00 1970 PST
+  186 |   6 | 00186              | Sat Mar 28 00:00:00 1970 PST
+  187 | 407 | 00187_update7      | Sun Mar 29 00:00:00 1970 PST
+  188 |   8 | 00188              | Mon Mar 30 00:00:00 1970 PST
+  189 | 509 | 00189_update9      | Tue Mar 31 00:00:00 1970 PST
+  190 |   0 | 00190              | Wed Apr 01 00:00:00 1970 PST
+  191 |   1 | 00191              | Thu Apr 02 00:00:00 1970 PST
+  193 | 303 | 00193_update3      | Sat Apr 04 00:00:00 1970 PST
+  194 |   4 | 00194              | Sun Apr 05 00:00:00 1970 PST
+  196 |   6 | 00196              | Tue Apr 07 00:00:00 1970 PST
+  197 | 407 | 00197_update7      | Wed Apr 08 00:00:00 1970 PST
+  198 |   8 | 00198              | Thu Apr 09 00:00:00 1970 PST
+  199 | 509 | 00199_update9      | Fri Apr 10 00:00:00 1970 PST
+  200 |   0 | 00200              | Thu Jan 01 00:00:00 1970 PST
+  201 |   1 | 00201              | Fri Jan 02 00:00:00 1970 PST
+  203 | 303 | 00203_update3      | Sun Jan 04 00:00:00 1970 PST
+  204 |   4 | 00204              | Mon Jan 05 00:00:00 1970 PST
+  206 |   6 | 00206              | Wed Jan 07 00:00:00 1970 PST
+  207 | 407 | 00207_update7      | Thu Jan 08 00:00:00 1970 PST
+  208 |   8 | 00208              | Fri Jan 09 00:00:00 1970 PST
+  209 | 509 | 00209_update9      | Sat Jan 10 00:00:00 1970 PST
+  210 |   0 | 00210              | Sun Jan 11 00:00:00 1970 PST
+  211 |   1 | 00211              | Mon Jan 12 00:00:00 1970 PST
+  213 | 303 | 00213_update3      | Wed Jan 14 00:00:00 1970 PST
+  214 |   4 | 00214              | Thu Jan 15 00:00:00 1970 PST
+  216 |   6 | 00216              | Sat Jan 17 00:00:00 1970 PST
+  217 | 407 | 00217_update7      | Sun Jan 18 00:00:00 1970 PST
+  218 |   8 | 00218              | Mon Jan 19 00:00:00 1970 PST
+  219 | 509 | 00219_update9      | Tue Jan 20 00:00:00 1970 PST
+  220 |   0 | 00220              | Wed Jan 21 00:00:00 1970 PST
+  221 |   1 | 00221              | Thu Jan 22 00:00:00 1970 PST
+  223 | 303 | 00223_update3      | Sat Jan 24 00:00:00 1970 PST
+  224 |   4 | 00224              | Sun Jan 25 00:00:00 1970 PST
+  226 |   6 | 00226              | Tue Jan 27 00:00:00 1970 PST
+  227 | 407 | 00227_update7      | Wed Jan 28 00:00:00 1970 PST
+  228 |   8 | 00228              | Thu Jan 29 00:00:00 1970 PST
+  229 | 509 | 00229_update9      | Fri Jan 30 00:00:00 1970 PST
+  230 |   0 | 00230              | Sat Jan 31 00:00:00 1970 PST
+  231 |   1 | 00231              | Sun Feb 01 00:00:00 1970 PST
+  233 | 303 | 00233_update3      | Tue Feb 03 00:00:00 1970 PST
+  234 |   4 | 00234              | Wed Feb 04 00:00:00 1970 PST
+  236 |   6 | 00236              | Fri Feb 06 00:00:00 1970 PST
+  237 | 407 | 00237_update7      | Sat Feb 07 00:00:00 1970 PST
+  238 |   8 | 00238              | Sun Feb 08 00:00:00 1970 PST
+  239 | 509 | 00239_update9      | Mon Feb 09 00:00:00 1970 PST
+  240 |   0 | 00240              | Tue Feb 10 00:00:00 1970 PST
+  241 |   1 | 00241              | Wed Feb 11 00:00:00 1970 PST
+  243 | 303 | 00243_update3      | Fri Feb 13 00:00:00 1970 PST
+  244 |   4 | 00244              | Sat Feb 14 00:00:00 1970 PST
+  246 |   6 | 00246              | Mon Feb 16 00:00:00 1970 PST
+  247 | 407 | 00247_update7      | Tue Feb 17 00:00:00 1970 PST
+  248 |   8 | 00248              | Wed Feb 18 00:00:00 1970 PST
+  249 | 509 | 00249_update9      | Thu Feb 19 00:00:00 1970 PST
+  250 |   0 | 00250              | Fri Feb 20 00:00:00 1970 PST
+  251 |   1 | 00251              | Sat Feb 21 00:00:00 1970 PST
+  253 | 303 | 00253_update3      | Mon Feb 23 00:00:00 1970 PST
+  254 |   4 | 00254              | Tue Feb 24 00:00:00 1970 PST
+  256 |   6 | 00256              | Thu Feb 26 00:00:00 1970 PST
+  257 | 407 | 00257_update7      | Fri Feb 27 00:00:00 1970 PST
+  258 |   8 | 00258              | Sat Feb 28 00:00:00 1970 PST
+  259 | 509 | 00259_update9      | Sun Mar 01 00:00:00 1970 PST
+  260 |   0 | 00260              | Mon Mar 02 00:00:00 1970 PST
+  261 |   1 | 00261              | Tue Mar 03 00:00:00 1970 PST
+  263 | 303 | 00263_update3      | Thu Mar 05 00:00:00 1970 PST
+  264 |   4 | 00264              | Fri Mar 06 00:00:00 1970 PST
+  266 |   6 | 00266              | Sun Mar 08 00:00:00 1970 PST
+  267 | 407 | 00267_update7      | Mon Mar 09 00:00:00 1970 PST
+  268 |   8 | 00268              | Tue Mar 10 00:00:00 1970 PST
+  269 | 509 | 00269_update9      | Wed Mar 11 00:00:00 1970 PST
+  270 |   0 | 00270              | Thu Mar 12 00:00:00 1970 PST
+  271 |   1 | 00271              | Fri Mar 13 00:00:00 1970 PST
+  273 | 303 | 00273_update3      | Sun Mar 15 00:00:00 1970 PST
+  274 |   4 | 00274              | Mon Mar 16 00:00:00 1970 PST
+  276 |   6 | 00276              | Wed Mar 18 00:00:00 1970 PST
+  277 | 407 | 00277_update7      | Thu Mar 19 00:00:00 1970 PST
+  278 |   8 | 00278              | Fri Mar 20 00:00:00 1970 PST
+  279 | 509 | 00279_update9      | Sat Mar 21 00:00:00 1970 PST
+  280 |   0 | 00280              | Sun Mar 22 00:00:00 1970 PST
+  281 |   1 | 00281              | Mon Mar 23 00:00:00 1970 PST
+  283 | 303 | 00283_update3      | Wed Mar 25 00:00:00 1970 PST
+  284 |   4 | 00284              | Thu Mar 26 00:00:00 1970 PST
+  286 |   6 | 00286              | Sat Mar 28 00:00:00 1970 PST
+  287 | 407 | 00287_update7      | Sun Mar 29 00:00:00 1970 PST
+  288 |   8 | 00288              | Mon Mar 30 00:00:00 1970 PST
+  289 | 509 | 00289_update9      | Tue Mar 31 00:00:00 1970 PST
+  290 |   0 | 00290              | Wed Apr 01 00:00:00 1970 PST
+  291 |   1 | 00291              | Thu Apr 02 00:00:00 1970 PST
+  293 | 303 | 00293_update3      | Sat Apr 04 00:00:00 1970 PST
+  294 |   4 | 00294              | Sun Apr 05 00:00:00 1970 PST
+  296 |   6 | 00296              | Tue Apr 07 00:00:00 1970 PST
+  297 | 407 | 00297_update7      | Wed Apr 08 00:00:00 1970 PST
+  298 |   8 | 00298              | Thu Apr 09 00:00:00 1970 PST
+  299 | 509 | 00299_update9      | Fri Apr 10 00:00:00 1970 PST
+  300 |   0 | 00300              | Thu Jan 01 00:00:00 1970 PST
+  301 |   1 | 00301              | Fri Jan 02 00:00:00 1970 PST
+  303 | 303 | 00303_update3      | Sun Jan 04 00:00:00 1970 PST
+  304 |   4 | 00304              | Mon Jan 05 00:00:00 1970 PST
+  306 |   6 | 00306              | Wed Jan 07 00:00:00 1970 PST
+  307 | 407 | 00307_update7      | Thu Jan 08 00:00:00 1970 PST
+  308 |   8 | 00308              | Fri Jan 09 00:00:00 1970 PST
+  309 | 509 | 00309_update9      | Sat Jan 10 00:00:00 1970 PST
+  310 |   0 | 00310              | Sun Jan 11 00:00:00 1970 PST
+  311 |   1 | 00311              | Mon Jan 12 00:00:00 1970 PST
+  313 | 303 | 00313_update3      | Wed Jan 14 00:00:00 1970 PST
+  314 |   4 | 00314              | Thu Jan 15 00:00:00 1970 PST
+  316 |   6 | 00316              | Sat Jan 17 00:00:00 1970 PST
+  317 | 407 | 00317_update7      | Sun Jan 18 00:00:00 1970 PST
+  318 |   8 | 00318              | Mon Jan 19 00:00:00 1970 PST
+  319 | 509 | 00319_update9      | Tue Jan 20 00:00:00 1970 PST
+  320 |   0 | 00320              | Wed Jan 21 00:00:00 1970 PST
+  321 |   1 | 00321              | Thu Jan 22 00:00:00 1970 PST
+  323 | 303 | 00323_update3      | Sat Jan 24 00:00:00 1970 PST
+  324 |   4 | 00324              | Sun Jan 25 00:00:00 1970 PST
+  326 |   6 | 00326              | Tue Jan 27 00:00:00 1970 PST
+  327 | 407 | 00327_update7      | Wed Jan 28 00:00:00 1970 PST
+  328 |   8 | 00328              | Thu Jan 29 00:00:00 1970 PST
+  329 | 509 | 00329_update9      | Fri Jan 30 00:00:00 1970 PST
+  330 |   0 | 00330              | Sat Jan 31 00:00:00 1970 PST
+  331 |   1 | 00331              | Sun Feb 01 00:00:00 1970 PST
+  333 | 303 | 00333_update3      | Tue Feb 03 00:00:00 1970 PST
+  334 |   4 | 00334              | Wed Feb 04 00:00:00 1970 PST
+  336 |   6 | 00336              | Fri Feb 06 00:00:00 1970 PST
+  337 | 407 | 00337_update7      | Sat Feb 07 00:00:00 1970 PST
+  338 |   8 | 00338              | Sun Feb 08 00:00:00 1970 PST
+  339 | 509 | 00339_update9      | Mon Feb 09 00:00:00 1970 PST
+  340 |   0 | 00340              | Tue Feb 10 00:00:00 1970 PST
+  341 |   1 | 00341              | Wed Feb 11 00:00:00 1970 PST
+  343 | 303 | 00343_update3      | Fri Feb 13 00:00:00 1970 PST
+  344 |   4 | 00344              | Sat Feb 14 00:00:00 1970 PST
+  346 |   6 | 00346              | Mon Feb 16 00:00:00 1970 PST
+  347 | 407 | 00347_update7      | Tue Feb 17 00:00:00 1970 PST
+  348 |   8 | 00348              | Wed Feb 18 00:00:00 1970 PST
+  349 | 509 | 00349_update9      | Thu Feb 19 00:00:00 1970 PST
+  350 |   0 | 00350              | Fri Feb 20 00:00:00 1970 PST
+  351 |   1 | 00351              | Sat Feb 21 00:00:00 1970 PST
+  353 | 303 | 00353_update3      | Mon Feb 23 00:00:00 1970 PST
+  354 |   4 | 00354              | Tue Feb 24 00:00:00 1970 PST
+  356 |   6 | 00356              | Thu Feb 26 00:00:00 1970 PST
+  357 | 407 | 00357_update7      | Fri Feb 27 00:00:00 1970 PST
+  358 |   8 | 00358              | Sat Feb 28 00:00:00 1970 PST
+  359 | 509 | 00359_update9      | Sun Mar 01 00:00:00 1970 PST
+  360 |   0 | 00360              | Mon Mar 02 00:00:00 1970 PST
+  361 |   1 | 00361              | Tue Mar 03 00:00:00 1970 PST
+  363 | 303 | 00363_update3      | Thu Mar 05 00:00:00 1970 PST
+  364 |   4 | 00364              | Fri Mar 06 00:00:00 1970 PST
+  366 |   6 | 00366              | Sun Mar 08 00:00:00 1970 PST
+  367 | 407 | 00367_update7      | Mon Mar 09 00:00:00 1970 PST
+  368 |   8 | 00368              | Tue Mar 10 00:00:00 1970 PST
+  369 | 509 | 00369_update9      | Wed Mar 11 00:00:00 1970 PST
+  370 |   0 | 00370              | Thu Mar 12 00:00:00 1970 PST
+  371 |   1 | 00371              | Fri Mar 13 00:00:00 1970 PST
+  373 | 303 | 00373_update3      | Sun Mar 15 00:00:00 1970 PST
+  374 |   4 | 00374              | Mon Mar 16 00:00:00 1970 PST
+  376 |   6 | 00376              | Wed Mar 18 00:00:00 1970 PST
+  377 | 407 | 00377_update7      | Thu Mar 19 00:00:00 1970 PST
+  378 |   8 | 00378              | Fri Mar 20 00:00:00 1970 PST
+  379 | 509 | 00379_update9      | Sat Mar 21 00:00:00 1970 PST
+  380 |   0 | 00380              | Sun Mar 22 00:00:00 1970 PST
+  381 |   1 | 00381              | Mon Mar 23 00:00:00 1970 PST
+  383 | 303 | 00383_update3      | Wed Mar 25 00:00:00 1970 PST
+  384 |   4 | 00384              | Thu Mar 26 00:00:00 1970 PST
+  386 |   6 | 00386              | Sat Mar 28 00:00:00 1970 PST
+  387 | 407 | 00387_update7      | Sun Mar 29 00:00:00 1970 PST
+  388 |   8 | 00388              | Mon Mar 30 00:00:00 1970 PST
+  389 | 509 | 00389_update9      | Tue Mar 31 00:00:00 1970 PST
+  390 |   0 | 00390              | Wed Apr 01 00:00:00 1970 PST
+  391 |   1 | 00391              | Thu Apr 02 00:00:00 1970 PST
+  393 | 303 | 00393_update3      | Sat Apr 04 00:00:00 1970 PST
+  394 |   4 | 00394              | Sun Apr 05 00:00:00 1970 PST
+  396 |   6 | 00396              | Tue Apr 07 00:00:00 1970 PST
+  397 | 407 | 00397_update7      | Wed Apr 08 00:00:00 1970 PST
+  398 |   8 | 00398              | Thu Apr 09 00:00:00 1970 PST
+  399 | 509 | 00399_update9      | Fri Apr 10 00:00:00 1970 PST
+  400 |   0 | 00400              | Thu Jan 01 00:00:00 1970 PST
+  401 |   1 | 00401              | Fri Jan 02 00:00:00 1970 PST
+  403 | 303 | 00403_update3      | Sun Jan 04 00:00:00 1970 PST
+  404 |   4 | 00404              | Mon Jan 05 00:00:00 1970 PST
+  406 |   6 | 00406              | Wed Jan 07 00:00:00 1970 PST
+  407 | 407 | 00407_update7      | Thu Jan 08 00:00:00 1970 PST
+  408 |   8 | 00408              | Fri Jan 09 00:00:00 1970 PST
+  409 | 509 | 00409_update9      | Sat Jan 10 00:00:00 1970 PST
+  410 |   0 | 00410              | Sun Jan 11 00:00:00 1970 PST
+  411 |   1 | 00411              | Mon Jan 12 00:00:00 1970 PST
+  413 | 303 | 00413_update3      | Wed Jan 14 00:00:00 1970 PST
+  414 |   4 | 00414              | Thu Jan 15 00:00:00 1970 PST
+  416 |   6 | 00416              | Sat Jan 17 00:00:00 1970 PST
+  417 | 407 | 00417_update7      | Sun Jan 18 00:00:00 1970 PST
+  418 |   8 | 00418              | Mon Jan 19 00:00:00 1970 PST
+  419 | 509 | 00419_update9      | Tue Jan 20 00:00:00 1970 PST
+  420 |   0 | 00420              | Wed Jan 21 00:00:00 1970 PST
+  421 |   1 | 00421              | Thu Jan 22 00:00:00 1970 PST
+  423 | 303 | 00423_update3      | Sat Jan 24 00:00:00 1970 PST
+  424 |   4 | 00424              | Sun Jan 25 00:00:00 1970 PST
+  426 |   6 | 00426              | Tue Jan 27 00:00:00 1970 PST
+  427 | 407 | 00427_update7      | Wed Jan 28 00:00:00 1970 PST
+  428 |   8 | 00428              | Thu Jan 29 00:00:00 1970 PST
+  429 | 509 | 00429_update9      | Fri Jan 30 00:00:00 1970 PST
+  430 |   0 | 00430              | Sat Jan 31 00:00:00 1970 PST
+  431 |   1 | 00431              | Sun Feb 01 00:00:00 1970 PST
+  433 | 303 | 00433_update3      | Tue Feb 03 00:00:00 1970 PST
+  434 |   4 | 00434              | Wed Feb 04 00:00:00 1970 PST
+  436 |   6 | 00436              | Fri Feb 06 00:00:00 1970 PST
+  437 | 407 | 00437_update7      | Sat Feb 07 00:00:00 1970 PST
+  438 |   8 | 00438              | Sun Feb 08 00:00:00 1970 PST
+  439 | 509 | 00439_update9      | Mon Feb 09 00:00:00 1970 PST
+  440 |   0 | 00440              | Tue Feb 10 00:00:00 1970 PST
+  441 |   1 | 00441              | Wed Feb 11 00:00:00 1970 PST
+  443 | 303 | 00443_update3      | Fri Feb 13 00:00:00 1970 PST
+  444 |   4 | 00444              | Sat Feb 14 00:00:00 1970 PST
+  446 |   6 | 00446              | Mon Feb 16 00:00:00 1970 PST
+  447 | 407 | 00447_update7      | Tue Feb 17 00:00:00 1970 PST
+  448 |   8 | 00448              | Wed Feb 18 00:00:00 1970 PST
+  449 | 509 | 00449_update9      | Thu Feb 19 00:00:00 1970 PST
+  450 |   0 | 00450              | Fri Feb 20 00:00:00 1970 PST
+  451 |   1 | 00451              | Sat Feb 21 00:00:00 1970 PST
+  453 | 303 | 00453_update3      | Mon Feb 23 00:00:00 1970 PST
+  454 |   4 | 00454              | Tue Feb 24 00:00:00 1970 PST
+  456 |   6 | 00456              | Thu Feb 26 00:00:00 1970 PST
+  457 | 407 | 00457_update7      | Fri Feb 27 00:00:00 1970 PST
+  458 |   8 | 00458              | Sat Feb 28 00:00:00 1970 PST
+  459 | 509 | 00459_update9      | Sun Mar 01 00:00:00 1970 PST
+  460 |   0 | 00460              | Mon Mar 02 00:00:00 1970 PST
+  461 |   1 | 00461              | Tue Mar 03 00:00:00 1970 PST
+  463 | 303 | 00463_update3      | Thu Mar 05 00:00:00 1970 PST
+  464 |   4 | 00464              | Fri Mar 06 00:00:00 1970 PST
+  466 |   6 | 00466              | Sun Mar 08 00:00:00 1970 PST
+  467 | 407 | 00467_update7      | Mon Mar 09 00:00:00 1970 PST
+  468 |   8 | 00468              | Tue Mar 10 00:00:00 1970 PST
+  469 | 509 | 00469_update9      | Wed Mar 11 00:00:00 1970 PST
+  470 |   0 | 00470              | Thu Mar 12 00:00:00 1970 PST
+  471 |   1 | 00471              | Fri Mar 13 00:00:00 1970 PST
+  473 | 303 | 00473_update3      | Sun Mar 15 00:00:00 1970 PST
+  474 |   4 | 00474              | Mon Mar 16 00:00:00 1970 PST
+  476 |   6 | 00476              | Wed Mar 18 00:00:00 1970 PST
+  477 | 407 | 00477_update7      | Thu Mar 19 00:00:00 1970 PST
+  478 |   8 | 00478              | Fri Mar 20 00:00:00 1970 PST
+  479 | 509 | 00479_update9      | Sat Mar 21 00:00:00 1970 PST
+  480 |   0 | 00480              | Sun Mar 22 00:00:00 1970 PST
+  481 |   1 | 00481              | Mon Mar 23 00:00:00 1970 PST
+  483 | 303 | 00483_update3      | Wed Mar 25 00:00:00 1970 PST
+  484 |   4 | 00484              | Thu Mar 26 00:00:00 1970 PST
+  486 |   6 | 00486              | Sat Mar 28 00:00:00 1970 PST
+  487 | 407 | 00487_update7      | Sun Mar 29 00:00:00 1970 PST
+  488 |   8 | 00488              | Mon Mar 30 00:00:00 1970 PST
+  489 | 509 | 00489_update9      | Tue Mar 31 00:00:00 1970 PST
+  490 |   0 | 00490              | Wed Apr 01 00:00:00 1970 PST
+  491 |   1 | 00491              | Thu Apr 02 00:00:00 1970 PST
+  493 | 303 | 00493_update3      | Sat Apr 04 00:00:00 1970 PST
+  494 |   4 | 00494              | Sun Apr 05 00:00:00 1970 PST
+  496 |   6 | 00496              | Tue Apr 07 00:00:00 1970 PST
+  497 | 407 | 00497_update7      | Wed Apr 08 00:00:00 1970 PST
+  498 |   8 | 00498              | Thu Apr 09 00:00:00 1970 PST
+  499 | 509 | 00499_update9      | Fri Apr 10 00:00:00 1970 PST
+  500 |   0 | 00500              | Thu Jan 01 00:00:00 1970 PST
+  501 |   1 | 00501              | Fri Jan 02 00:00:00 1970 PST
+  503 | 303 | 00503_update3      | Sun Jan 04 00:00:00 1970 PST
+  504 |   4 | 00504              | Mon Jan 05 00:00:00 1970 PST
+  506 |   6 | 00506              | Wed Jan 07 00:00:00 1970 PST
+  507 | 407 | 00507_update7      | Thu Jan 08 00:00:00 1970 PST
+  508 |   8 | 00508              | Fri Jan 09 00:00:00 1970 PST
+  509 | 509 | 00509_update9      | Sat Jan 10 00:00:00 1970 PST
+  510 |   0 | 00510              | Sun Jan 11 00:00:00 1970 PST
+  511 |   1 | 00511              | Mon Jan 12 00:00:00 1970 PST
+  513 | 303 | 00513_update3      | Wed Jan 14 00:00:00 1970 PST
+  514 |   4 | 00514              | Thu Jan 15 00:00:00 1970 PST
+  516 |   6 | 00516              | Sat Jan 17 00:00:00 1970 PST
+  517 | 407 | 00517_update7      | Sun Jan 18 00:00:00 1970 PST
+  518 |   8 | 00518              | Mon Jan 19 00:00:00 1970 PST
+  519 | 509 | 00519_update9      | Tue Jan 20 00:00:00 1970 PST
+  520 |   0 | 00520              | Wed Jan 21 00:00:00 1970 PST
+  521 |   1 | 00521              | Thu Jan 22 00:00:00 1970 PST
+  523 | 303 | 00523_update3      | Sat Jan 24 00:00:00 1970 PST
+  524 |   4 | 00524              | Sun Jan 25 00:00:00 1970 PST
+  526 |   6 | 00526              | Tue Jan 27 00:00:00 1970 PST
+  527 | 407 | 00527_update7      | Wed Jan 28 00:00:00 1970 PST
+  528 |   8 | 00528              | Thu Jan 29 00:00:00 1970 PST
+  529 | 509 | 00529_update9      | Fri Jan 30 00:00:00 1970 PST
+  530 |   0 | 00530              | Sat Jan 31 00:00:00 1970 PST
+  531 |   1 | 00531              | Sun Feb 01 00:00:00 1970 PST
+  533 | 303 | 00533_update3      | Tue Feb 03 00:00:00 1970 PST
+  534 |   4 | 00534              | Wed Feb 04 00:00:00 1970 PST
+  536 |   6 | 00536              | Fri Feb 06 00:00:00 1970 PST
+  537 | 407 | 00537_update7      | Sat Feb 07 00:00:00 1970 PST
+  538 |   8 | 00538              | Sun Feb 08 00:00:00 1970 PST
+  539 | 509 | 00539_update9      | Mon Feb 09 00:00:00 1970 PST
+  540 |   0 | 00540              | Tue Feb 10 00:00:00 1970 PST
+  541 |   1 | 00541              | Wed Feb 11 00:00:00 1970 PST
+  543 | 303 | 00543_update3      | Fri Feb 13 00:00:00 1970 PST
+  544 |   4 | 00544              | Sat Feb 14 00:00:00 1970 PST
+  546 |   6 | 00546              | Mon Feb 16 00:00:00 1970 PST
+  547 | 407 | 00547_update7      | Tue Feb 17 00:00:00 1970 PST
+  548 |   8 | 00548              | Wed Feb 18 00:00:00 1970 PST
+  549 | 509 | 00549_update9      | Thu Feb 19 00:00:00 1970 PST
+  550 |   0 | 00550              | Fri Feb 20 00:00:00 1970 PST
+  551 |   1 | 00551              | Sat Feb 21 00:00:00 1970 PST
+  553 | 303 | 00553_update3      | Mon Feb 23 00:00:00 1970 PST
+  554 |   4 | 00554              | Tue Feb 24 00:00:00 1970 PST
+  556 |   6 | 00556              | Thu Feb 26 00:00:00 1970 PST
+  557 | 407 | 00557_update7      | Fri Feb 27 00:00:00 1970 PST
+  558 |   8 | 00558              | Sat Feb 28 00:00:00 1970 PST
+  559 | 509 | 00559_update9      | Sun Mar 01 00:00:00 1970 PST
+  560 |   0 | 00560              | Mon Mar 02 00:00:00 1970 PST
+  561 |   1 | 00561              | Tue Mar 03 00:00:00 1970 PST
+  563 | 303 | 00563_update3      | Thu Mar 05 00:00:00 1970 PST
+  564 |   4 | 00564              | Fri Mar 06 00:00:00 1970 PST
+  566 |   6 | 00566              | Sun Mar 08 00:00:00 1970 PST
+  567 | 407 | 00567_update7      | Mon Mar 09 00:00:00 1970 PST
+  568 |   8 | 00568              | Tue Mar 10 00:00:00 1970 PST
+  569 | 509 | 00569_update9      | Wed Mar 11 00:00:00 1970 PST
+  570 |   0 | 00570              | Thu Mar 12 00:00:00 1970 PST
+  571 |   1 | 00571              | Fri Mar 13 00:00:00 1970 PST
+  573 | 303 | 00573_update3      | Sun Mar 15 00:00:00 1970 PST
+  574 |   4 | 00574              | Mon Mar 16 00:00:00 1970 PST
+  576 |   6 | 00576              | Wed Mar 18 00:00:00 1970 PST
+  577 | 407 | 00577_update7      | Thu Mar 19 00:00:00 1970 PST
+  578 |   8 | 00578              | Fri Mar 20 00:00:00 1970 PST
+  579 | 509 | 00579_update9      | Sat Mar 21 00:00:00 1970 PST
+  580 |   0 | 00580              | Sun Mar 22 00:00:00 1970 PST
+  581 |   1 | 00581              | Mon Mar 23 00:00:00 1970 PST
+  583 | 303 | 00583_update3      | Wed Mar 25 00:00:00 1970 PST
+  584 |   4 | 00584              | Thu Mar 26 00:00:00 1970 PST
+  586 |   6 | 00586              | Sat Mar 28 00:00:00 1970 PST
+  587 | 407 | 00587_update7      | Sun Mar 29 00:00:00 1970 PST
+  588 |   8 | 00588              | Mon Mar 30 00:00:00 1970 PST
+  589 | 509 | 00589_update9      | Tue Mar 31 00:00:00 1970 PST
+  590 |   0 | 00590              | Wed Apr 01 00:00:00 1970 PST
+  591 |   1 | 00591              | Thu Apr 02 00:00:00 1970 PST
+  593 | 303 | 00593_update3      | Sat Apr 04 00:00:00 1970 PST
+  594 |   4 | 00594              | Sun Apr 05 00:00:00 1970 PST
+  596 |   6 | 00596              | Tue Apr 07 00:00:00 1970 PST
+  597 | 407 | 00597_update7      | Wed Apr 08 00:00:00 1970 PST
+  598 |   8 | 00598              | Thu Apr 09 00:00:00 1970 PST
+  599 | 509 | 00599_update9      | Fri Apr 10 00:00:00 1970 PST
+  600 |   0 | 00600              | Thu Jan 01 00:00:00 1970 PST
+  601 |   1 | 00601              | Fri Jan 02 00:00:00 1970 PST
+  603 | 303 | 00603_update3      | Sun Jan 04 00:00:00 1970 PST
+  604 |   4 | 00604              | Mon Jan 05 00:00:00 1970 PST
+  606 |   6 | 00606              | Wed Jan 07 00:00:00 1970 PST
+  607 | 407 | 00607_update7      | Thu Jan 08 00:00:00 1970 PST
+  608 |   8 | 00608              | Fri Jan 09 00:00:00 1970 PST
+  609 | 509 | 00609_update9      | Sat Jan 10 00:00:00 1970 PST
+  610 |   0 | 00610              | Sun Jan 11 00:00:00 1970 PST
+  611 |   1 | 00611              | Mon Jan 12 00:00:00 1970 PST
+  613 | 303 | 00613_update3      | Wed Jan 14 00:00:00 1970 PST
+  614 |   4 | 00614              | Thu Jan 15 00:00:00 1970 PST
+  616 |   6 | 00616              | Sat Jan 17 00:00:00 1970 PST
+  617 | 407 | 00617_update7      | Sun Jan 18 00:00:00 1970 PST
+  618 |   8 | 00618              | Mon Jan 19 00:00:00 1970 PST
+  619 | 509 | 00619_update9      | Tue Jan 20 00:00:00 1970 PST
+  620 |   0 | 00620              | Wed Jan 21 00:00:00 1970 PST
+  621 |   1 | 00621              | Thu Jan 22 00:00:00 1970 PST
+  623 | 303 | 00623_update3      | Sat Jan 24 00:00:00 1970 PST
+  624 |   4 | 00624              | Sun Jan 25 00:00:00 1970 PST
+  626 |   6 | 00626              | Tue Jan 27 00:00:00 1970 PST
+  627 | 407 | 00627_update7      | Wed Jan 28 00:00:00 1970 PST
+  628 |   8 | 00628              | Thu Jan 29 00:00:00 1970 PST
+  629 | 509 | 00629_update9      | Fri Jan 30 00:00:00 1970 PST
+  630 |   0 | 00630              | Sat Jan 31 00:00:00 1970 PST
+  631 |   1 | 00631              | Sun Feb 01 00:00:00 1970 PST
+  633 | 303 | 00633_update3      | Tue Feb 03 00:00:00 1970 PST
+  634 |   4 | 00634              | Wed Feb 04 00:00:00 1970 PST
+  636 |   6 | 00636              | Fri Feb 06 00:00:00 1970 PST
+  637 | 407 | 00637_update7      | Sat Feb 07 00:00:00 1970 PST
+  638 |   8 | 00638              | Sun Feb 08 00:00:00 1970 PST
+  639 | 509 | 00639_update9      | Mon Feb 09 00:00:00 1970 PST
+  640 |   0 | 00640              | Tue Feb 10 00:00:00 1970 PST
+  641 |   1 | 00641              | Wed Feb 11 00:00:00 1970 PST
+  643 | 303 | 00643_update3      | Fri Feb 13 00:00:00 1970 PST
+  644 |   4 | 00644              | Sat Feb 14 00:00:00 1970 PST
+  646 |   6 | 00646              | Mon Feb 16 00:00:00 1970 PST
+  647 | 407 | 00647_update7      | Tue Feb 17 00:00:00 1970 PST
+  648 |   8 | 00648              | Wed Feb 18 00:00:00 1970 PST
+  649 | 509 | 00649_update9      | Thu Feb 19 00:00:00 1970 PST
+  650 |   0 | 00650              | Fri Feb 20 00:00:00 1970 PST
+  651 |   1 | 00651              | Sat Feb 21 00:00:00 1970 PST
+  653 | 303 | 00653_update3      | Mon Feb 23 00:00:00 1970 PST
+  654 |   4 | 00654              | Tue Feb 24 00:00:00 1970 PST
+  656 |   6 | 00656              | Thu Feb 26 00:00:00 1970 PST
+  657 | 407 | 00657_update7      | Fri Feb 27 00:00:00 1970 PST
+  658 |   8 | 00658              | Sat Feb 28 00:00:00 1970 PST
+  659 | 509 | 00659_update9      | Sun Mar 01 00:00:00 1970 PST
+  660 |   0 | 00660              | Mon Mar 02 00:00:00 1970 PST
+  661 |   1 | 00661              | Tue Mar 03 00:00:00 1970 PST
+  663 | 303 | 00663_update3      | Thu Mar 05 00:00:00 1970 PST
+  664 |   4 | 00664              | Fri Mar 06 00:00:00 1970 PST
+  666 |   6 | 00666              | Sun Mar 08 00:00:00 1970 PST
+  667 | 407 | 00667_update7      | Mon Mar 09 00:00:00 1970 PST
+  668 |   8 | 00668              | Tue Mar 10 00:00:00 1970 PST
+  669 | 509 | 00669_update9      | Wed Mar 11 00:00:00 1970 PST
+  670 |   0 | 00670              | Thu Mar 12 00:00:00 1970 PST
+  671 |   1 | 00671              | Fri Mar 13 00:00:00 1970 PST
+  673 | 303 | 00673_update3      | Sun Mar 15 00:00:00 1970 PST
+  674 |   4 | 00674              | Mon Mar 16 00:00:00 1970 PST
+  676 |   6 | 00676              | Wed Mar 18 00:00:00 1970 PST
+  677 | 407 | 00677_update7      | Thu Mar 19 00:00:00 1970 PST
+  678 |   8 | 00678              | Fri Mar 20 00:00:00 1970 PST
+  679 | 509 | 00679_update9      | Sat Mar 21 00:00:00 1970 PST
+  680 |   0 | 00680              | Sun Mar 22 00:00:00 1970 PST
+  681 |   1 | 00681              | Mon Mar 23 00:00:00 1970 PST
+  683 | 303 | 00683_update3      | Wed Mar 25 00:00:00 1970 PST
+  684 |   4 | 00684              | Thu Mar 26 00:00:00 1970 PST
+  686 |   6 | 00686              | Sat Mar 28 00:00:00 1970 PST
+  687 | 407 | 00687_update7      | Sun Mar 29 00:00:00 1970 PST
+  688 |   8 | 00688              | Mon Mar 30 00:00:00 1970 PST
+  689 | 509 | 00689_update9      | Tue Mar 31 00:00:00 1970 PST
+  690 |   0 | 00690              | Wed Apr 01 00:00:00 1970 PST
+  691 |   1 | 00691              | Thu Apr 02 00:00:00 1970 PST
+  693 | 303 | 00693_update3      | Sat Apr 04 00:00:00 1970 PST
+  694 |   4 | 00694              | Sun Apr 05 00:00:00 1970 PST
+  696 |   6 | 00696              | Tue Apr 07 00:00:00 1970 PST
+  697 | 407 | 00697_update7      | Wed Apr 08 00:00:00 1970 PST
+  698 |   8 | 00698              | Thu Apr 09 00:00:00 1970 PST
+  699 | 509 | 00699_update9      | Fri Apr 10 00:00:00 1970 PST
+  700 |   0 | 00700              | Thu Jan 01 00:00:00 1970 PST
+  701 |   1 | 00701              | Fri Jan 02 00:00:00 1970 PST
+  703 | 303 | 00703_update3      | Sun Jan 04 00:00:00 1970 PST
+  704 |   4 | 00704              | Mon Jan 05 00:00:00 1970 PST
+  706 |   6 | 00706              | Wed Jan 07 00:00:00 1970 PST
+  707 | 407 | 00707_update7      | Thu Jan 08 00:00:00 1970 PST
+  708 |   8 | 00708              | Fri Jan 09 00:00:00 1970 PST
+  709 | 509 | 00709_update9      | Sat Jan 10 00:00:00 1970 PST
+  710 |   0 | 00710              | Sun Jan 11 00:00:00 1970 PST
+  711 |   1 | 00711              | Mon Jan 12 00:00:00 1970 PST
+  713 | 303 | 00713_update3      | Wed Jan 14 00:00:00 1970 PST
+  714 |   4 | 00714              | Thu Jan 15 00:00:00 1970 PST
+  716 |   6 | 00716              | Sat Jan 17 00:00:00 1970 PST
+  717 | 407 | 00717_update7      | Sun Jan 18 00:00:00 1970 PST
+  718 |   8 | 00718              | Mon Jan 19 00:00:00 1970 PST
+  719 | 509 | 00719_update9      | Tue Jan 20 00:00:00 1970 PST
+  720 |   0 | 00720              | Wed Jan 21 00:00:00 1970 PST
+  721 |   1 | 00721              | Thu Jan 22 00:00:00 1970 PST
+  723 | 303 | 00723_update3      | Sat Jan 24 00:00:00 1970 PST
+  724 |   4 | 00724              | Sun Jan 25 00:00:00 1970 PST
+  726 |   6 | 00726              | Tue Jan 27 00:00:00 1970 PST
+  727 | 407 | 00727_update7      | Wed Jan 28 00:00:00 1970 PST
+  728 |   8 | 00728              | Thu Jan 29 00:00:00 1970 PST
+  729 | 509 | 00729_update9      | Fri Jan 30 00:00:00 1970 PST
+  730 |   0 | 00730              | Sat Jan 31 00:00:00 1970 PST
+  731 |   1 | 00731              | Sun Feb 01 00:00:00 1970 PST
+  733 | 303 | 00733_update3      | Tue Feb 03 00:00:00 1970 PST
+  734 |   4 | 00734              | Wed Feb 04 00:00:00 1970 PST
+  736 |   6 | 00736              | Fri Feb 06 00:00:00 1970 PST
+  737 | 407 | 00737_update7      | Sat Feb 07 00:00:00 1970 PST
+  738 |   8 | 00738              | Sun Feb 08 00:00:00 1970 PST
+  739 | 509 | 00739_update9      | Mon Feb 09 00:00:00 1970 PST
+  740 |   0 | 00740              | Tue Feb 10 00:00:00 1970 PST
+  741 |   1 | 00741              | Wed Feb 11 00:00:00 1970 PST
+  743 | 303 | 00743_update3      | Fri Feb 13 00:00:00 1970 PST
+  744 |   4 | 00744              | Sat Feb 14 00:00:00 1970 PST
+  746 |   6 | 00746              | Mon Feb 16 00:00:00 1970 PST
+  747 | 407 | 00747_update7      | Tue Feb 17 00:00:00 1970 PST
+  748 |   8 | 00748              | Wed Feb 18 00:00:00 1970 PST
+  749 | 509 | 00749_update9      | Thu Feb 19 00:00:00 1970 PST
+  750 |   0 | 00750              | Fri Feb 20 00:00:00 1970 PST
+  751 |   1 | 00751              | Sat Feb 21 00:00:00 1970 PST
+  753 | 303 | 00753_update3      | Mon Feb 23 00:00:00 1970 PST
+  754 |   4 | 00754              | Tue Feb 24 00:00:00 1970 PST
+  756 |   6 | 00756              | Thu Feb 26 00:00:00 1970 PST
+  757 | 407 | 00757_update7      | Fri Feb 27 00:00:00 1970 PST
+  758 |   8 | 00758              | Sat Feb 28 00:00:00 1970 PST
+  759 | 509 | 00759_update9      | Sun Mar 01 00:00:00 1970 PST
+  760 |   0 | 00760              | Mon Mar 02 00:00:00 1970 PST
+  761 |   1 | 00761              | Tue Mar 03 00:00:00 1970 PST
+  763 | 303 | 00763_update3      | Thu Mar 05 00:00:00 1970 PST
+  764 |   4 | 00764              | Fri Mar 06 00:00:00 1970 PST
+  766 |   6 | 00766              | Sun Mar 08 00:00:00 1970 PST
+  767 | 407 | 00767_update7      | Mon Mar 09 00:00:00 1970 PST
+  768 |   8 | 00768              | Tue Mar 10 00:00:00 1970 PST
+  769 | 509 | 00769_update9      | Wed Mar 11 00:00:00 1970 PST
+  770 |   0 | 00770              | Thu Mar 12 00:00:00 1970 PST
+  771 |   1 | 00771              | Fri Mar 13 00:00:00 1970 PST
+  773 | 303 | 00773_update3      | Sun Mar 15 00:00:00 1970 PST
+  774 |   4 | 00774              | Mon Mar 16 00:00:00 1970 PST
+  776 |   6 | 00776              | Wed Mar 18 00:00:00 1970 PST
+  777 | 407 | 00777_update7      | Thu Mar 19 00:00:00 1970 PST
+  778 |   8 | 00778              | Fri Mar 20 00:00:00 1970 PST
+  779 | 509 | 00779_update9      | Sat Mar 21 00:00:00 1970 PST
+  780 |   0 | 00780              | Sun Mar 22 00:00:00 1970 PST
+  781 |   1 | 00781              | Mon Mar 23 00:00:00 1970 PST
+  783 | 303 | 00783_update3      | Wed Mar 25 00:00:00 1970 PST
+  784 |   4 | 00784              | Thu Mar 26 00:00:00 1970 PST
+  786 |   6 | 00786              | Sat Mar 28 00:00:00 1970 PST
+  787 | 407 | 00787_update7      | Sun Mar 29 00:00:00 1970 PST
+  788 |   8 | 00788              | Mon Mar 30 00:00:00 1970 PST
+  789 | 509 | 00789_update9      | Tue Mar 31 00:00:00 1970 PST
+  790 |   0 | 00790              | Wed Apr 01 00:00:00 1970 PST
+  791 |   1 | 00791              | Thu Apr 02 00:00:00 1970 PST
+  793 | 303 | 00793_update3      | Sat Apr 04 00:00:00 1970 PST
+  794 |   4 | 00794              | Sun Apr 05 00:00:00 1970 PST
+  796 |   6 | 00796              | Tue Apr 07 00:00:00 1970 PST
+  797 | 407 | 00797_update7      | Wed Apr 08 00:00:00 1970 PST
+  798 |   8 | 00798              | Thu Apr 09 00:00:00 1970 PST
+  799 | 509 | 00799_update9      | Fri Apr 10 00:00:00 1970 PST
+  800 |   0 | 00800              | Thu Jan 01 00:00:00 1970 PST
+  801 |   1 | 00801              | Fri Jan 02 00:00:00 1970 PST
+  803 | 303 | 00803_update3      | Sun Jan 04 00:00:00 1970 PST
+  804 |   4 | 00804              | Mon Jan 05 00:00:00 1970 PST
+  806 |   6 | 00806              | Wed Jan 07 00:00:00 1970 PST
+  807 | 407 | 00807_update7      | Thu Jan 08 00:00:00 1970 PST
+  808 |   8 | 00808              | Fri Jan 09 00:00:00 1970 PST
+  809 | 509 | 00809_update9      | Sat Jan 10 00:00:00 1970 PST
+  810 |   0 | 00810              | Sun Jan 11 00:00:00 1970 PST
+  811 |   1 | 00811              | Mon Jan 12 00:00:00 1970 PST
+  813 | 303 | 00813_update3      | Wed Jan 14 00:00:00 1970 PST
+  814 |   4 | 00814              | Thu Jan 15 00:00:00 1970 PST
+  816 |   6 | 00816              | Sat Jan 17 00:00:00 1970 PST
+  817 | 407 | 00817_update7      | Sun Jan 18 00:00:00 1970 PST
+  818 |   8 | 00818              | Mon Jan 19 00:00:00 1970 PST
+  819 | 509 | 00819_update9      | Tue Jan 20 00:00:00 1970 PST
+  820 |   0 | 00820              | Wed Jan 21 00:00:00 1970 PST
+  821 |   1 | 00821              | Thu Jan 22 00:00:00 1970 PST
+  823 | 303 | 00823_update3      | Sat Jan 24 00:00:00 1970 PST
+  824 |   4 | 00824              | Sun Jan 25 00:00:00 1970 PST
+  826 |   6 | 00826              | Tue Jan 27 00:00:00 1970 PST
+  827 | 407 | 00827_update7      | Wed Jan 28 00:00:00 1970 PST
+  828 |   8 | 00828              | Thu Jan 29 00:00:00 1970 PST
+  829 | 509 | 00829_update9      | Fri Jan 30 00:00:00 1970 PST
+  830 |   0 | 00830              | Sat Jan 31 00:00:00 1970 PST
+  831 |   1 | 00831              | Sun Feb 01 00:00:00 1970 PST
+  833 | 303 | 00833_update3      | Tue Feb 03 00:00:00 1970 PST
+  834 |   4 | 00834              | Wed Feb 04 00:00:00 1970 PST
+  836 |   6 | 00836              | Fri Feb 06 00:00:00 1970 PST
+  837 | 407 | 00837_update7      | Sat Feb 07 00:00:00 1970 PST
+  838 |   8 | 00838              | Sun Feb 08 00:00:00 1970 PST
+  839 | 509 | 00839_update9      | Mon Feb 09 00:00:00 1970 PST
+  840 |   0 | 00840              | Tue Feb 10 00:00:00 1970 PST
+  841 |   1 | 00841              | Wed Feb 11 00:00:00 1970 PST
+  843 | 303 | 00843_update3      | Fri Feb 13 00:00:00 1970 PST
+  844 |   4 | 00844              | Sat Feb 14 00:00:00 1970 PST
+  846 |   6 | 00846              | Mon Feb 16 00:00:00 1970 PST
+  847 | 407 | 00847_update7      | Tue Feb 17 00:00:00 1970 PST
+  848 |   8 | 00848              | Wed Feb 18 00:00:00 1970 PST
+  849 | 509 | 00849_update9      | Thu Feb 19 00:00:00 1970 PST
+  850 |   0 | 00850              | Fri Feb 20 00:00:00 1970 PST
+  851 |   1 | 00851              | Sat Feb 21 00:00:00 1970 PST
+  853 | 303 | 00853_update3      | Mon Feb 23 00:00:00 1970 PST
+  854 |   4 | 00854              | Tue Feb 24 00:00:00 1970 PST
+  856 |   6 | 00856              | Thu Feb 26 00:00:00 1970 PST
+  857 | 407 | 00857_update7      | Fri Feb 27 00:00:00 1970 PST
+  858 |   8 | 00858              | Sat Feb 28 00:00:00 1970 PST
+  859 | 509 | 00859_update9      | Sun Mar 01 00:00:00 1970 PST
+  860 |   0 | 00860              | Mon Mar 02 00:00:00 1970 PST
+  861 |   1 | 00861              | Tue Mar 03 00:00:00 1970 PST
+  863 | 303 | 00863_update3      | Thu Mar 05 00:00:00 1970 PST
+  864 |   4 | 00864              | Fri Mar 06 00:00:00 1970 PST
+  866 |   6 | 00866              | Sun Mar 08 00:00:00 1970 PST
+  867 | 407 | 00867_update7      | Mon Mar 09 00:00:00 1970 PST
+  868 |   8 | 00868              | Tue Mar 10 00:00:00 1970 PST
+  869 | 509 | 00869_update9      | Wed Mar 11 00:00:00 1970 PST
+  870 |   0 | 00870              | Thu Mar 12 00:00:00 1970 PST
+  871 |   1 | 00871              | Fri Mar 13 00:00:00 1970 PST
+  873 | 303 | 00873_update3      | Sun Mar 15 00:00:00 1970 PST
+  874 |   4 | 00874              | Mon Mar 16 00:00:00 1970 PST
+  876 |   6 | 00876              | Wed Mar 18 00:00:00 1970 PST
+  877 | 407 | 00877_update7      | Thu Mar 19 00:00:00 1970 PST
+  878 |   8 | 00878              | Fri Mar 20 00:00:00 1970 PST
+  879 | 509 | 00879_update9      | Sat Mar 21 00:00:00 1970 PST
+  880 |   0 | 00880              | Sun Mar 22 00:00:00 1970 PST
+  881 |   1 | 00881              | Mon Mar 23 00:00:00 1970 PST
+  883 | 303 | 00883_update3      | Wed Mar 25 00:00:00 1970 PST
+  884 |   4 | 00884              | Thu Mar 26 00:00:00 1970 PST
+  886 |   6 | 00886              | Sat Mar 28 00:00:00 1970 PST
+  887 | 407 | 00887_update7      | Sun Mar 29 00:00:00 1970 PST
+  888 |   8 | 00888              | Mon Mar 30 00:00:00 1970 PST
+  889 | 509 | 00889_update9      | Tue Mar 31 00:00:00 1970 PST
+  890 |   0 | 00890              | Wed Apr 01 00:00:00 1970 PST
+  891 |   1 | 00891              | Thu Apr 02 00:00:00 1970 PST
+  893 | 303 | 00893_update3      | Sat Apr 04 00:00:00 1970 PST
+  894 |   4 | 00894              | Sun Apr 05 00:00:00 1970 PST
+  896 |   6 | 00896              | Tue Apr 07 00:00:00 1970 PST
+  897 | 407 | 00897_update7      | Wed Apr 08 00:00:00 1970 PST
+  898 |   8 | 00898              | Thu Apr 09 00:00:00 1970 PST
+  899 | 509 | 00899_update9      | Fri Apr 10 00:00:00 1970 PST
+  900 |   0 | 00900              | Thu Jan 01 00:00:00 1970 PST
+  901 |   1 | 00901              | Fri Jan 02 00:00:00 1970 PST
+  903 | 303 | 00903_update3      | Sun Jan 04 00:00:00 1970 PST
+  904 |   4 | 00904              | Mon Jan 05 00:00:00 1970 PST
+  906 |   6 | 00906              | Wed Jan 07 00:00:00 1970 PST
+  907 | 407 | 00907_update7      | Thu Jan 08 00:00:00 1970 PST
+  908 |   8 | 00908              | Fri Jan 09 00:00:00 1970 PST
+  909 | 509 | 00909_update9      | Sat Jan 10 00:00:00 1970 PST
+  910 |   0 | 00910              | Sun Jan 11 00:00:00 1970 PST
+  911 |   1 | 00911              | Mon Jan 12 00:00:00 1970 PST
+  913 | 303 | 00913_update3      | Wed Jan 14 00:00:00 1970 PST
+  914 |   4 | 00914              | Thu Jan 15 00:00:00 1970 PST
+  916 |   6 | 00916              | Sat Jan 17 00:00:00 1970 PST
+  917 | 407 | 00917_update7      | Sun Jan 18 00:00:00 1970 PST
+  918 |   8 | 00918              | Mon Jan 19 00:00:00 1970 PST
+  919 | 509 | 00919_update9      | Tue Jan 20 00:00:00 1970 PST
+  920 |   0 | 00920              | Wed Jan 21 00:00:00 1970 PST
+  921 |   1 | 00921              | Thu Jan 22 00:00:00 1970 PST
+  923 | 303 | 00923_update3      | Sat Jan 24 00:00:00 1970 PST
+  924 |   4 | 00924              | Sun Jan 25 00:00:00 1970 PST
+  926 |   6 | 00926              | Tue Jan 27 00:00:00 1970 PST
+  927 | 407 | 00927_update7      | Wed Jan 28 00:00:00 1970 PST
+  928 |   8 | 00928              | Thu Jan 29 00:00:00 1970 PST
+  929 | 509 | 00929_update9      | Fri Jan 30 00:00:00 1970 PST
+  930 |   0 | 00930              | Sat Jan 31 00:00:00 1970 PST
+  931 |   1 | 00931              | Sun Feb 01 00:00:00 1970 PST
+  933 | 303 | 00933_update3      | Tue Feb 03 00:00:00 1970 PST
+  934 |   4 | 00934              | Wed Feb 04 00:00:00 1970 PST
+  936 |   6 | 00936              | Fri Feb 06 00:00:00 1970 PST
+  937 | 407 | 00937_update7      | Sat Feb 07 00:00:00 1970 PST
+  938 |   8 | 00938              | Sun Feb 08 00:00:00 1970 PST
+  939 | 509 | 00939_update9      | Mon Feb 09 00:00:00 1970 PST
+  940 |   0 | 00940              | Tue Feb 10 00:00:00 1970 PST
+  941 |   1 | 00941              | Wed Feb 11 00:00:00 1970 PST
+  943 | 303 | 00943_update3      | Fri Feb 13 00:00:00 1970 PST
+  944 |   4 | 00944              | Sat Feb 14 00:00:00 1970 PST
+  946 |   6 | 00946              | Mon Feb 16 00:00:00 1970 PST
+  947 | 407 | 00947_update7      | Tue Feb 17 00:00:00 1970 PST
+  948 |   8 | 00948              | Wed Feb 18 00:00:00 1970 PST
+  949 | 509 | 00949_update9      | Thu Feb 19 00:00:00 1970 PST
+  950 |   0 | 00950              | Fri Feb 20 00:00:00 1970 PST
+  951 |   1 | 00951              | Sat Feb 21 00:00:00 1970 PST
+  953 | 303 | 00953_update3      | Mon Feb 23 00:00:00 1970 PST
+  954 |   4 | 00954              | Tue Feb 24 00:00:00 1970 PST
+  956 |   6 | 00956              | Thu Feb 26 00:00:00 1970 PST
+  957 | 407 | 00957_update7      | Fri Feb 27 00:00:00 1970 PST
+  958 |   8 | 00958              | Sat Feb 28 00:00:00 1970 PST
+  959 | 509 | 00959_update9      | Sun Mar 01 00:00:00 1970 PST
+  960 |   0 | 00960              | Mon Mar 02 00:00:00 1970 PST
+  961 |   1 | 00961              | Tue Mar 03 00:00:00 1970 PST
+  963 | 303 | 00963_update3      | Thu Mar 05 00:00:00 1970 PST
+  964 |   4 | 00964              | Fri Mar 06 00:00:00 1970 PST
+  966 |   6 | 00966              | Sun Mar 08 00:00:00 1970 PST
+  967 | 407 | 00967_update7      | Mon Mar 09 00:00:00 1970 PST
+  968 |   8 | 00968              | Tue Mar 10 00:00:00 1970 PST
+  969 | 509 | 00969_update9      | Wed Mar 11 00:00:00 1970 PST
+  970 |   0 | 00970              | Thu Mar 12 00:00:00 1970 PST
+  971 |   1 | 00971              | Fri Mar 13 00:00:00 1970 PST
+  973 | 303 | 00973_update3      | Sun Mar 15 00:00:00 1970 PST
+  974 |   4 | 00974              | Mon Mar 16 00:00:00 1970 PST
+  976 |   6 | 00976              | Wed Mar 18 00:00:00 1970 PST
+  977 | 407 | 00977_update7      | Thu Mar 19 00:00:00 1970 PST
+  978 |   8 | 00978              | Fri Mar 20 00:00:00 1970 PST
+  979 | 509 | 00979_update9      | Sat Mar 21 00:00:00 1970 PST
+  980 |   0 | 00980              | Sun Mar 22 00:00:00 1970 PST
+  981 |   1 | 00981              | Mon Mar 23 00:00:00 1970 PST
+  983 | 303 | 00983_update3      | Wed Mar 25 00:00:00 1970 PST
+  984 |   4 | 00984              | Thu Mar 26 00:00:00 1970 PST
+  986 |   6 | 00986              | Sat Mar 28 00:00:00 1970 PST
+  987 | 407 | 00987_update7      | Sun Mar 29 00:00:00 1970 PST
+  988 |   8 | 00988              | Mon Mar 30 00:00:00 1970 PST
+  989 | 509 | 00989_update9      | Tue Mar 31 00:00:00 1970 PST
+  990 |   0 | 00990              | Wed Apr 01 00:00:00 1970 PST
+  991 |   1 | 00991              | Thu Apr 02 00:00:00 1970 PST
+  993 | 303 | 00993_update3      | Sat Apr 04 00:00:00 1970 PST
+  994 |   4 | 00994              | Sun Apr 05 00:00:00 1970 PST
+  996 |   6 | 00996              | Tue Apr 07 00:00:00 1970 PST
+  997 | 407 | 00997_update7      | Wed Apr 08 00:00:00 1970 PST
+  998 |   8 | 00998              | Thu Apr 09 00:00:00 1970 PST
+  999 | 509 | 00999_update9      | Fri Apr 10 00:00:00 1970 PST
+ 1000 |   0 | 01000              | Thu Jan 01 00:00:00 1970 PST
+ 1001 | 101 | 0000100001         | 
+ 1003 | 403 | 0000300003_update3 | 
+ 1004 | 104 | 0000400004         | 
+ 1006 | 106 | 0000600006         | 
+ 1007 | 507 | 0000700007_update7 | 
+ 1008 | 108 | 0000800008         | 
+ 1009 | 609 | 0000900009_update9 | 
+ 1010 | 100 | 0001000010         | 
+ 1011 | 101 | 0001100011         | 
+ 1013 | 403 | 0001300013_update3 | 
+ 1014 | 104 | 0001400014         | 
+ 1016 | 106 | 0001600016         | 
+ 1017 | 507 | 0001700017_update7 | 
+ 1018 | 108 | 0001800018         | 
+ 1019 | 609 | 0001900019_update9 | 
+ 1020 | 100 | 0002000020         | 
+ 1101 | 201 | aaa                | 
+ 1103 | 503 | ccc_update3        | 
+ 1104 | 204 | ddd                | 
+(819 rows)
+
+-- Test that defaults and triggers on remote table work as expected
+ALTER TABLE "S 1"."T 1" ALTER c6 SET DEFAULT '(^-^;)';
+CREATE OR REPLACE FUNCTION "S 1".F_BRTRIG() RETURNS trigger AS $$
+BEGIN
+    NEW.c3 = NEW.c3 || '_trig_update';
+    RETURN NEW;
+END;
+$$ LANGUAGE plpgsql;
+CREATE TRIGGER t1_br_insert BEFORE INSERT OR UPDATE
+    ON "S 1"."T 1" FOR EACH ROW EXECUTE PROCEDURE "S 1".F_BRTRIG();
+INSERT INTO ft2 (c1,c2,c3) VALUES (1208, 218, 'fff') RETURNING *;
+  c1  | c2  |       c3        | c4 | c5 |   c6   | c7 | c8 
+------+-----+-----------------+----+----+--------+----+----
+ 1208 | 218 | fff_trig_update |    |    | (^-^;) |    | 
+(1 row)
+
+INSERT INTO ft2 (c1,c2,c3,c6) VALUES (1218, 218, 'ggg', '(--;') RETURNING *;
+  c1  | c2  |       c3        | c4 | c5 |  c6  | c7 | c8 
+------+-----+-----------------+----+----+------+----+----
+ 1218 | 218 | ggg_trig_update |    |    | (--; |    | 
+(1 row)
+
+UPDATE ft2 SET c2 = c2 + 600 WHERE c1 % 10 = 8 RETURNING *;
+  c1  | c2  |             c3              |              c4              |            c5            |   c6   |     c7     | c8  
+------+-----+-----------------------------+------------------------------+--------------------------+--------+------------+-----
+    8 | 608 | 00008_trig_update           | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8      | 8          | foo
+   18 | 608 | 00018_trig_update           | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8      | 8          | foo
+   28 | 608 | 00028_trig_update           | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8      | 8          | foo
+   38 | 608 | 00038_trig_update           | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8      | 8          | foo
+   48 | 608 | 00048_trig_update           | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8      | 8          | foo
+   58 | 608 | 00058_trig_update           | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8      | 8          | foo
+   68 | 608 | 00068_trig_update           | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8      | 8          | foo
+   78 | 608 | 00078_trig_update           | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8      | 8          | foo
+   88 | 608 | 00088_trig_update           | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8      | 8          | foo
+   98 | 608 | 00098_trig_update           | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8      | 8          | foo
+  108 | 608 | 00108_trig_update           | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8      | 8          | foo
+  118 | 608 | 00118_trig_update           | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8      | 8          | foo
+  128 | 608 | 00128_trig_update           | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8      | 8          | foo
+  138 | 608 | 00138_trig_update           | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8      | 8          | foo
+  148 | 608 | 00148_trig_update           | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8      | 8          | foo
+  158 | 608 | 00158_trig_update           | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8      | 8          | foo
+  168 | 608 | 00168_trig_update           | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8      | 8          | foo
+  178 | 608 | 00178_trig_update           | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8      | 8          | foo
+  188 | 608 | 00188_trig_update           | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8      | 8          | foo
+  198 | 608 | 00198_trig_update           | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8      | 8          | foo
+  208 | 608 | 00208_trig_update           | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8      | 8          | foo
+  218 | 608 | 00218_trig_update           | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8      | 8          | foo
+  228 | 608 | 00228_trig_update           | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8      | 8          | foo
+  238 | 608 | 00238_trig_update           | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8      | 8          | foo
+  248 | 608 | 00248_trig_update           | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8      | 8          | foo
+  258 | 608 | 00258_trig_update           | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8      | 8          | foo
+  268 | 608 | 00268_trig_update           | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8      | 8          | foo
+  278 | 608 | 00278_trig_update           | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8      | 8          | foo
+  288 | 608 | 00288_trig_update           | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8      | 8          | foo
+  298 | 608 | 00298_trig_update           | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8      | 8          | foo
+  308 | 608 | 00308_trig_update           | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8      | 8          | foo
+  318 | 608 | 00318_trig_update           | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8      | 8          | foo
+  328 | 608 | 00328_trig_update           | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8      | 8          | foo
+  338 | 608 | 00338_trig_update           | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8      | 8          | foo
+  348 | 608 | 00348_trig_update           | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8      | 8          | foo
+  358 | 608 | 00358_trig_update           | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8      | 8          | foo
+  368 | 608 | 00368_trig_update           | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8      | 8          | foo
+  378 | 608 | 00378_trig_update           | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8      | 8          | foo
+  388 | 608 | 00388_trig_update           | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8      | 8          | foo
+  398 | 608 | 00398_trig_update           | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8      | 8          | foo
+  408 | 608 | 00408_trig_update           | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8      | 8          | foo
+  418 | 608 | 00418_trig_update           | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8      | 8          | foo
+  428 | 608 | 00428_trig_update           | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8      | 8          | foo
+  438 | 608 | 00438_trig_update           | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8      | 8          | foo
+  448 | 608 | 00448_trig_update           | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8      | 8          | foo
+  458 | 608 | 00458_trig_update           | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8      | 8          | foo
+  468 | 608 | 00468_trig_update           | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8      | 8          | foo
+  478 | 608 | 00478_trig_update           | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8      | 8          | foo
+  488 | 608 | 00488_trig_update           | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8      | 8          | foo
+  498 | 608 | 00498_trig_update           | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8      | 8          | foo
+  508 | 608 | 00508_trig_update           | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8      | 8          | foo
+  518 | 608 | 00518_trig_update           | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8      | 8          | foo
+  528 | 608 | 00528_trig_update           | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8      | 8          | foo
+  538 | 608 | 00538_trig_update           | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8      | 8          | foo
+  548 | 608 | 00548_trig_update           | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8      | 8          | foo
+  558 | 608 | 00558_trig_update           | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8      | 8          | foo
+  568 | 608 | 00568_trig_update           | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8      | 8          | foo
+  578 | 608 | 00578_trig_update           | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8      | 8          | foo
+  588 | 608 | 00588_trig_update           | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8      | 8          | foo
+  598 | 608 | 00598_trig_update           | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8      | 8          | foo
+  608 | 608 | 00608_trig_update           | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8      | 8          | foo
+  618 | 608 | 00618_trig_update           | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8      | 8          | foo
+  628 | 608 | 00628_trig_update           | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8      | 8          | foo
+  638 | 608 | 00638_trig_update           | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8      | 8          | foo
+  648 | 608 | 00648_trig_update           | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8      | 8          | foo
+  658 | 608 | 00658_trig_update           | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8      | 8          | foo
+  668 | 608 | 00668_trig_update           | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8      | 8          | foo
+  678 | 608 | 00678_trig_update           | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8      | 8          | foo
+  688 | 608 | 00688_trig_update           | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8      | 8          | foo
+  698 | 608 | 00698_trig_update           | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8      | 8          | foo
+  708 | 608 | 00708_trig_update           | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8      | 8          | foo
+  718 | 608 | 00718_trig_update           | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8      | 8          | foo
+  728 | 608 | 00728_trig_update           | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8      | 8          | foo
+  738 | 608 | 00738_trig_update           | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8      | 8          | foo
+  748 | 608 | 00748_trig_update           | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8      | 8          | foo
+  758 | 608 | 00758_trig_update           | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8      | 8          | foo
+  768 | 608 | 00768_trig_update           | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8      | 8          | foo
+  778 | 608 | 00778_trig_update           | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8      | 8          | foo
+  788 | 608 | 00788_trig_update           | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8      | 8          | foo
+  798 | 608 | 00798_trig_update           | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8      | 8          | foo
+  808 | 608 | 00808_trig_update           | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8      | 8          | foo
+  818 | 608 | 00818_trig_update           | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8      | 8          | foo
+  828 | 608 | 00828_trig_update           | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8      | 8          | foo
+  838 | 608 | 00838_trig_update           | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8      | 8          | foo
+  848 | 608 | 00848_trig_update           | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8      | 8          | foo
+  858 | 608 | 00858_trig_update           | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8      | 8          | foo
+  868 | 608 | 00868_trig_update           | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8      | 8          | foo
+  878 | 608 | 00878_trig_update           | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8      | 8          | foo
+  888 | 608 | 00888_trig_update           | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8      | 8          | foo
+  898 | 608 | 00898_trig_update           | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8      | 8          | foo
+  908 | 608 | 00908_trig_update           | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8      | 8          | foo
+  918 | 608 | 00918_trig_update           | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8      | 8          | foo
+  928 | 608 | 00928_trig_update           | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8      | 8          | foo
+  938 | 608 | 00938_trig_update           | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8      | 8          | foo
+  948 | 608 | 00948_trig_update           | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8      | 8          | foo
+  958 | 608 | 00958_trig_update           | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8      | 8          | foo
+  968 | 608 | 00968_trig_update           | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8      | 8          | foo
+  978 | 608 | 00978_trig_update           | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8      | 8          | foo
+  988 | 608 | 00988_trig_update           | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8      | 8          | foo
+  998 | 608 | 00998_trig_update           | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8      | 8          | foo
+ 1008 | 708 | 0000800008_trig_update      |                              |                          |        |            | 
+ 1018 | 708 | 0001800018_trig_update      |                              |                          |        |            | 
+ 1208 | 818 | fff_trig_update_trig_update |                              |                          | (^-^;) |            | 
+ 1218 | 818 | ggg_trig_update_trig_update |                              |                          | (--;   |            | 
+(104 rows)
+
+-- Test errors thrown on remote side during update
+ALTER TABLE "S 1"."T 1" ADD CONSTRAINT c2positive CHECK (c2 >= 0);
+INSERT INTO ft1(c1, c2) VALUES(11, 12);  -- duplicate key
+ERROR:  duplicate key value violates unique constraint "t1_pkey"
+DETAIL:  Key ("C 1")=(11) already exists.
+CONTEXT:  Remote SQL command: INSERT INTO "S 1"."T 1"("C 1", c2) VALUES ($1, $2)
+INSERT INTO ft1(c1, c2) VALUES(1111, -2);  -- c2positive
+ERROR:  new row for relation "T 1" violates check constraint "c2positive"
+DETAIL:  Failing row contains (1111, -2, null, null, null, (^-^;), null, null).
+CONTEXT:  Remote SQL command: INSERT INTO "S 1"."T 1"("C 1", c2) VALUES ($1, $2)
+UPDATE ft1 SET c2 = -c2 WHERE c1 = 1;  -- c2positive
+ERROR:  new row for relation "T 1" violates check constraint "c2positive"
+DETAIL:  Failing row contains (1, -1, 00001_trig_update, 1970-01-02 03:00:00-05, 1970-01-02 00:00:00, 1, 1         , foo).
+CONTEXT:  Remote SQL command: UPDATE "S 1"."T 1" SET c2 = $2 WHERE ctid = $1
+-- Test savepoint/rollback behavior
+select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
+ c2  | count 
+-----+-------
+   0 |   100
+   1 |   100
+   4 |   100
+   6 |   100
+ 100 |     2
+ 101 |     2
+ 104 |     2
+ 106 |     2
+ 201 |     1
+ 204 |     1
+ 303 |   100
+ 403 |     2
+ 407 |   100
+(13 rows)
+
+select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1;
+ c2  | count 
+-----+-------
+   0 |   100
+   1 |   100
+   4 |   100
+   6 |   100
+ 100 |     2
+ 101 |     2
+ 104 |     2
+ 106 |     2
+ 201 |     1
+ 204 |     1
+ 303 |   100
+ 403 |     2
+ 407 |   100
+(13 rows)
+
+begin;
+update ft2 set c2 = 42 where c2 = 0;
+select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
+ c2  | count 
+-----+-------
+   1 |   100
+   4 |   100
+   6 |   100
+  42 |   100
+ 100 |     2
+ 101 |     2
+ 104 |     2
+ 106 |     2
+ 201 |     1
+ 204 |     1
+ 303 |   100
+ 403 |     2
+ 407 |   100
+(13 rows)
+
+savepoint s1;
+update ft2 set c2 = 44 where c2 = 4;
+select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
+ c2  | count 
+-----+-------
+   1 |   100
+   6 |   100
+  42 |   100
+  44 |   100
+ 100 |     2
+ 101 |     2
+ 104 |     2
+ 106 |     2
+ 201 |     1
+ 204 |     1
+ 303 |   100
+ 403 |     2
+ 407 |   100
+(13 rows)
+
+release savepoint s1;
+select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
+ c2  | count 
+-----+-------
+   1 |   100
+   6 |   100
+  42 |   100
+  44 |   100
+ 100 |     2
+ 101 |     2
+ 104 |     2
+ 106 |     2
+ 201 |     1
+ 204 |     1
+ 303 |   100
+ 403 |     2
+ 407 |   100
+(13 rows)
+
+savepoint s2;
+update ft2 set c2 = 46 where c2 = 6;
+select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
+ c2  | count 
+-----+-------
+   1 |   100
+  42 |   100
+  44 |   100
+  46 |   100
+ 100 |     2
+ 101 |     2
+ 104 |     2
+ 106 |     2
+ 201 |     1
+ 204 |     1
+ 303 |   100
+ 403 |     2
+ 407 |   100
+(13 rows)
+
+rollback to savepoint s2;
+select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
+ c2  | count 
+-----+-------
+   1 |   100
+   6 |   100
+  42 |   100
+  44 |   100
+ 100 |     2
+ 101 |     2
+ 104 |     2
+ 106 |     2
+ 201 |     1
+ 204 |     1
+ 303 |   100
+ 403 |     2
+ 407 |   100
+(13 rows)
+
+release savepoint s2;
+select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
+ c2  | count 
+-----+-------
+   1 |   100
+   6 |   100
+  42 |   100
+  44 |   100
+ 100 |     2
+ 101 |     2
+ 104 |     2
+ 106 |     2
+ 201 |     1
+ 204 |     1
+ 303 |   100
+ 403 |     2
+ 407 |   100
+(13 rows)
+
+savepoint s3;
+update ft2 set c2 = -2 where c2 = 42; -- fail on remote side
+ERROR:  new row for relation "T 1" violates check constraint "c2positive"
+DETAIL:  Failing row contains (10, -2, 00010_trig_update_trig_update, 1970-01-11 03:00:00-05, 1970-01-11 00:00:00, 0, 0         , foo).
+CONTEXT:  Remote SQL command: UPDATE "S 1"."T 1" SET c2 = $2 WHERE ctid = $1
+rollback to savepoint s3;
+select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
+ c2  | count 
+-----+-------
+   1 |   100
+   6 |   100
+  42 |   100
+  44 |   100
+ 100 |     2
+ 101 |     2
+ 104 |     2
+ 106 |     2
+ 201 |     1
+ 204 |     1
+ 303 |   100
+ 403 |     2
+ 407 |   100
+(13 rows)
+
+release savepoint s3;
+select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
+ c2  | count 
+-----+-------
+   1 |   100
+   6 |   100
+  42 |   100
+  44 |   100
+ 100 |     2
+ 101 |     2
+ 104 |     2
+ 106 |     2
+ 201 |     1
+ 204 |     1
+ 303 |   100
+ 403 |     2
+ 407 |   100
+(13 rows)
+
+-- none of the above is committed yet remotely
+select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1;
+ c2  | count 
+-----+-------
+   0 |   100
+   1 |   100
+   4 |   100
+   6 |   100
+ 100 |     2
+ 101 |     2
+ 104 |     2
+ 106 |     2
+ 201 |     1
+ 204 |     1
+ 303 |   100
+ 403 |     2
+ 407 |   100
+(13 rows)
+
+commit;
+select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
+ c2  | count 
+-----+-------
+   1 |   100
+   6 |   100
+  42 |   100
+  44 |   100
+ 100 |     2
+ 101 |     2
+ 104 |     2
+ 106 |     2
+ 201 |     1
+ 204 |     1
+ 303 |   100
+ 403 |     2
+ 407 |   100
+(13 rows)
+
+select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1;
+ c2  | count 
+-----+-------
+   1 |   100
+   6 |   100
+  42 |   100
+  44 |   100
+ 100 |     2
+ 101 |     2
+ 104 |     2
+ 106 |     2
+ 201 |     1
+ 204 |     1
+ 303 |   100
+ 403 |     2
+ 407 |   100
+(13 rows)
+
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index a46597f02eaeb0a35c566373173c420d65547f66..a6db061d603194de1477fa0b07e8d8a13996ae01 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -15,16 +15,21 @@
 #include "postgres_fdw.h"
 
 #include "access/htup_details.h"
+#include "access/sysattr.h"
 #include "commands/defrem.h"
 #include "commands/explain.h"
 #include "commands/vacuum.h"
 #include "foreign/fdwapi.h"
 #include "funcapi.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "optimizer/cost.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/planmain.h"
+#include "optimizer/prep.h"
+#include "optimizer/var.h"
 #include "parser/parsetree.h"
+#include "utils/builtins.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 
@@ -58,7 +63,7 @@ typedef struct PgFdwRelationInfo
 } PgFdwRelationInfo;
 
 /*
- * Indexes of FDW-private information stored in fdw_private list.
+ * Indexes of FDW-private information stored in fdw_private lists.
  *
  * We store various information in ForeignScan.fdw_private to pass it from
  * planner to executor.  Specifically there is:
@@ -66,26 +71,41 @@ typedef struct PgFdwRelationInfo
  * 1) SELECT statement text to be sent to the remote server
  * 2) IDs of PARAM_EXEC Params used in the SELECT statement
  *
- * These items are indexed with the enum FdwPrivateIndex, so an item can be
- * fetched with list_nth().  For example, to get the SELECT statement:
- *		sql = strVal(list_nth(fdw_private, FdwPrivateSelectSql));
+ * These items are indexed with the enum FdwScanPrivateIndex, so an item
+ * can be fetched with list_nth().	For example, to get the SELECT statement:
+ *		sql = strVal(list_nth(fdw_private, FdwScanPrivateSelectSql));
  */
-enum FdwPrivateIndex
+enum FdwScanPrivateIndex
 {
 	/* SQL statement to execute remotely (as a String node) */
-	FdwPrivateSelectSql,
-
+	FdwScanPrivateSelectSql,
 	/* Integer list of param IDs of PARAM_EXEC Params used in SQL stmt */
-	FdwPrivateExternParamIds,
+	FdwScanPrivateExternParamIds
+};
 
-	/* # of elements stored in the list fdw_private */
-	FdwPrivateNum
+/*
+ * Similarly, this enum describes what's kept in the fdw_private list for
+ * a ModifyTable node referencing a postgres_fdw foreign table.  We store:
+ *
+ * 1) INSERT/UPDATE/DELETE statement text to be sent to the remote server
+ * 2) Integer list of target attribute numbers for INSERT/UPDATE
+ *	  (NIL for a DELETE)
+ * 3) Boolean flag showing if there's a RETURNING clause
+ */
+enum FdwModifyPrivateIndex
+{
+	/* SQL statement to execute remotely (as a String node) */
+	FdwModifyPrivateUpdateSql,
+	/* Integer list of target attribute numbers for INSERT/UPDATE */
+	FdwModifyPrivateTargetAttnums,
+	/* has-returning flag (as an integer Value node) */
+	FdwModifyPrivateHasReturning
 };
 
 /*
  * Execution state of a foreign scan using postgres_fdw.
  */
-typedef struct PgFdwExecutionState
+typedef struct PgFdwScanState
 {
 	Relation	rel;			/* relcache entry for the foreign table */
 	AttInMetadata *attinmeta;	/* attribute datatype conversion metadata */
@@ -113,7 +133,33 @@ typedef struct PgFdwExecutionState
 	/* working memory contexts */
 	MemoryContext batch_cxt;	/* context holding current batch of tuples */
 	MemoryContext temp_cxt;		/* context for per-tuple temporary data */
-} PgFdwExecutionState;
+} PgFdwScanState;
+
+/*
+ * Execution state of a foreign insert/update/delete operation.
+ */
+typedef struct PgFdwModifyState
+{
+	Relation	rel;			/* relcache entry for the foreign table */
+	AttInMetadata *attinmeta;	/* attribute datatype conversion metadata */
+
+	/* for remote query execution */
+	PGconn	   *conn;			/* connection for the scan */
+	char	   *p_name;			/* name of prepared statement, if created */
+
+	/* extracted fdw_private data */
+	char	   *query;			/* text of INSERT/UPDATE/DELETE command */
+	List	   *target_attrs;	/* list of target attribute numbers */
+	bool		has_returning;	/* is there a RETURNING clause? */
+
+	/* info about parameters for prepared statement */
+	AttrNumber	ctidAttno;		/* attnum of input resjunk ctid column */
+	int			p_nums;			/* number of parameters to transmit */
+	FmgrInfo   *p_flinfo;		/* output conversion functions for them */
+
+	/* working memory context */
+	MemoryContext temp_cxt;		/* context for per-tuple temporary data */
+} PgFdwModifyState;
 
 /*
  * Workspace for analyzing a foreign table.
@@ -169,12 +215,43 @@ static ForeignScan *postgresGetForeignPlan(PlannerInfo *root,
 					   ForeignPath *best_path,
 					   List *tlist,
 					   List *scan_clauses);
-static void postgresExplainForeignScan(ForeignScanState *node,
-						   ExplainState *es);
 static void postgresBeginForeignScan(ForeignScanState *node, int eflags);
 static TupleTableSlot *postgresIterateForeignScan(ForeignScanState *node);
 static void postgresReScanForeignScan(ForeignScanState *node);
 static void postgresEndForeignScan(ForeignScanState *node);
+static void postgresAddForeignUpdateTargets(Query *parsetree,
+								RangeTblEntry *target_rte,
+								Relation target_relation);
+static List *postgresPlanForeignModify(PlannerInfo *root,
+						  ModifyTable *plan,
+						  Index resultRelation,
+						  int subplan_index);
+static void postgresBeginForeignModify(ModifyTableState *mtstate,
+						   ResultRelInfo *resultRelInfo,
+						   List *fdw_private,
+						   int subplan_index,
+						   int eflags);
+static TupleTableSlot *postgresExecForeignInsert(EState *estate,
+						  ResultRelInfo *resultRelInfo,
+						  TupleTableSlot *slot,
+						  TupleTableSlot *planSlot);
+static TupleTableSlot *postgresExecForeignUpdate(EState *estate,
+						  ResultRelInfo *resultRelInfo,
+						  TupleTableSlot *slot,
+						  TupleTableSlot *planSlot);
+static TupleTableSlot *postgresExecForeignDelete(EState *estate,
+						  ResultRelInfo *resultRelInfo,
+						  TupleTableSlot *slot,
+						  TupleTableSlot *planSlot);
+static void postgresEndForeignModify(EState *estate,
+						 ResultRelInfo *resultRelInfo);
+static void postgresExplainForeignScan(ForeignScanState *node,
+						   ExplainState *es);
+static void postgresExplainForeignModify(ModifyTableState *mtstate,
+							 ResultRelInfo *rinfo,
+							 List *fdw_private,
+							 int subplan_index,
+							 ExplainState *es);
 static bool postgresAnalyzeForeignTable(Relation relation,
 							AcquireSampleRowsFunc *func,
 							BlockNumber *totalpages);
@@ -191,6 +268,12 @@ static void get_remote_estimate(const char *sql,
 static void create_cursor(ForeignScanState *node);
 static void fetch_more_data(ForeignScanState *node);
 static void close_cursor(PGconn *conn, unsigned int cursor_number);
+static void prepare_foreign_modify(PgFdwModifyState *fmstate);
+static const char **convert_prep_stmt_params(PgFdwModifyState *fmstate,
+						 ItemPointer tupleid,
+						 TupleTableSlot *slot);
+static void store_returning_result(PgFdwModifyState *fmstate,
+					   TupleTableSlot *slot, PGresult *res);
 static int postgresAcquireSampleRowsFunc(Relation relation, int elevel,
 							  HeapTuple *rows, int targrows,
 							  double *totalrows,
@@ -214,17 +297,29 @@ postgres_fdw_handler(PG_FUNCTION_ARGS)
 {
 	FdwRoutine *routine = makeNode(FdwRoutine);
 
-	/* Required handler functions. */
+	/* Functions for scanning foreign tables */
 	routine->GetForeignRelSize = postgresGetForeignRelSize;
 	routine->GetForeignPaths = postgresGetForeignPaths;
 	routine->GetForeignPlan = postgresGetForeignPlan;
-	routine->ExplainForeignScan = postgresExplainForeignScan;
 	routine->BeginForeignScan = postgresBeginForeignScan;
 	routine->IterateForeignScan = postgresIterateForeignScan;
 	routine->ReScanForeignScan = postgresReScanForeignScan;
 	routine->EndForeignScan = postgresEndForeignScan;
 
-	/* Optional handler functions. */
+	/* Functions for updating foreign tables */
+	routine->AddForeignUpdateTargets = postgresAddForeignUpdateTargets;
+	routine->PlanForeignModify = postgresPlanForeignModify;
+	routine->BeginForeignModify = postgresBeginForeignModify;
+	routine->ExecForeignInsert = postgresExecForeignInsert;
+	routine->ExecForeignUpdate = postgresExecForeignUpdate;
+	routine->ExecForeignDelete = postgresExecForeignDelete;
+	routine->EndForeignModify = postgresEndForeignModify;
+
+	/* Support functions for EXPLAIN */
+	routine->ExplainForeignScan = postgresExplainForeignScan;
+	routine->ExplainForeignModify = postgresExplainForeignModify;
+
+	/* Support functions for ANALYZE */
 	routine->AnalyzeForeignTable = postgresAnalyzeForeignTable;
 
 	PG_RETURN_POINTER(routine);
@@ -249,7 +344,6 @@ postgresGetForeignRelSize(PlannerInfo *root,
 						  Oid foreigntableid)
 {
 	bool		use_remote_estimate = false;
-	ListCell   *lc;
 	PgFdwRelationInfo *fpinfo;
 	StringInfo	sql;
 	ForeignTable *table;
@@ -266,12 +360,14 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	List	   *param_conds;
 	List	   *local_conds;
 	List	   *param_numbers;
+	Bitmapset  *attrs_used;
+	ListCell   *lc;
 
 	/*
 	 * We use PgFdwRelationInfo to pass various information to subsequent
 	 * functions.
 	 */
-	fpinfo = palloc0(sizeof(PgFdwRelationInfo));
+	fpinfo = (PgFdwRelationInfo *) palloc0(sizeof(PgFdwRelationInfo));
 	initStringInfo(&fpinfo->sql);
 	sql = &fpinfo->sql;
 
@@ -303,16 +399,37 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	}
 
 	/*
-	 * Construct remote query which consists of SELECT, FROM, and WHERE
-	 * clauses.  Conditions which contain any Param node are excluded because
-	 * placeholder can't be used in EXPLAIN statement.  Such conditions are
-	 * appended later.
+	 * Identify which restriction clauses can be sent to the remote server and
+	 * which can't.  Conditions that are remotely executable but contain
+	 * PARAM_EXTERN Params have to be treated separately because we can't use
+	 * placeholders in remote EXPLAIN.
 	 */
 	classifyConditions(root, baserel, &remote_conds, &param_conds,
 					   &local_conds, &param_numbers);
-	deparseSimpleSql(sql, root, baserel, local_conds);
-	if (list_length(remote_conds) > 0)
-		appendWhereClause(sql, true, remote_conds, root);
+
+	/*
+	 * Identify which attributes will need to be retrieved from the remote
+	 * server.	These include all attrs needed for joins or final output, plus
+	 * all attrs used in the local_conds.
+	 */
+	attrs_used = NULL;
+	pull_varattnos((Node *) baserel->reltargetlist, baserel->relid,
+				   &attrs_used);
+	foreach(lc, local_conds)
+	{
+		RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+		pull_varattnos((Node *) rinfo->clause, baserel->relid,
+					   &attrs_used);
+	}
+
+	/*
+	 * Construct remote query which consists of SELECT, FROM, and WHERE
+	 * clauses.  For now, leave out the param_conds.
+	 */
+	deparseSelectSql(sql, root, baserel, attrs_used);
+	if (remote_conds)
+		appendWhereClause(sql, root, remote_conds, true);
 
 	/*
 	 * If the table or the server is configured to use remote estimates,
@@ -336,7 +453,7 @@ postgresGetForeignRelSize(PlannerInfo *root,
 		userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
 
 		user = GetUserMapping(userid, server->serverid);
-		conn = GetConnection(server, user);
+		conn = GetConnection(server, user, false);
 		get_remote_estimate(sql->data, conn, &rows, &width,
 							&startup_cost, &total_cost);
 		ReleaseConnection(conn);
@@ -403,11 +520,53 @@ postgresGetForeignRelSize(PlannerInfo *root,
 
 	/*
 	 * Finish deparsing remote query by adding conditions which were unusable
-	 * in remote EXPLAIN since they contain Param nodes.
+	 * in remote EXPLAIN because they contain Param nodes.
 	 */
-	if (list_length(param_conds) > 0)
-		appendWhereClause(sql, !(list_length(remote_conds) > 0), param_conds,
-						  root);
+	if (param_conds)
+		appendWhereClause(sql, root, param_conds, (remote_conds == NIL));
+
+	/*
+	 * Add FOR UPDATE/SHARE if appropriate.  We apply locking during the
+	 * initial row fetch, rather than later on as is done for local tables.
+	 * The extra roundtrips involved in trying to duplicate the local
+	 * semantics exactly don't seem worthwhile (see also comments for
+	 * RowMarkType).
+	 */
+	if (baserel->relid == root->parse->resultRelation &&
+		(root->parse->commandType == CMD_UPDATE ||
+		 root->parse->commandType == CMD_DELETE))
+	{
+		/* Relation is UPDATE/DELETE target, so use FOR UPDATE */
+		appendStringInfo(sql, " FOR UPDATE");
+	}
+	else
+	{
+		RowMarkClause *rc = get_parse_rowmark(root->parse, baserel->relid);
+
+		if (rc)
+		{
+			/*
+			 * Relation is specified as a FOR UPDATE/SHARE target, so handle
+			 * that.
+			 *
+			 * For now, just ignore any [NO] KEY specification, since (a) it's
+			 * not clear what that means for a remote table that we don't have
+			 * complete information about, and (b) it wouldn't work anyway on
+			 * older remote servers.  Likewise, we don't worry about NOWAIT.
+			 */
+			switch (rc->strength)
+			{
+				case LCS_FORKEYSHARE:
+				case LCS_FORSHARE:
+					appendStringInfo(sql, " FOR SHARE");
+					break;
+				case LCS_FORNOKEYUPDATE:
+				case LCS_FORUPDATE:
+					appendStringInfo(sql, " FOR UPDATE");
+					break;
+			}
+		}
+	}
 
 	/*
 	 * Store obtained information into FDW-private area of RelOptInfo so it's
@@ -477,7 +636,7 @@ postgresGetForeignPaths(PlannerInfo *root,
 
 	/*
 	 * Build the fdw_private list that will be available to the executor.
-	 * Items in the list must match enum FdwPrivateIndex, above.
+	 * Items in the list must match enum FdwScanPrivateIndex, above.
 	 */
 	fdw_private = list_make2(makeString(fpinfo->sql.data),
 							 fpinfo->param_numbers);
@@ -573,24 +732,6 @@ postgresGetForeignPlan(PlannerInfo *root,
 							fdw_private);
 }
 
-/*
- * postgresExplainForeignScan
- *		Produce extra output for EXPLAIN
- */
-static void
-postgresExplainForeignScan(ForeignScanState *node, ExplainState *es)
-{
-	List	   *fdw_private;
-	char	   *sql;
-
-	if (es->verbose)
-	{
-		fdw_private = ((ForeignScan *) node->ss.ps.plan)->fdw_private;
-		sql = strVal(list_nth(fdw_private, FdwPrivateSelectSql));
-		ExplainPropertyText("Remote SQL", sql, es);
-	}
-}
-
 /*
  * postgresBeginForeignScan
  *		Initiate an executor scan of a foreign PostgreSQL table.
@@ -600,7 +741,7 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 {
 	ForeignScan *fsplan = (ForeignScan *) node->ss.ps.plan;
 	EState	   *estate = node->ss.ps.state;
-	PgFdwExecutionState *festate;
+	PgFdwScanState *fsstate;
 	RangeTblEntry *rte;
 	Oid			userid;
 	ForeignTable *table;
@@ -619,8 +760,8 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 	/*
 	 * We'll save private state in node->fdw_state.
 	 */
-	festate = (PgFdwExecutionState *) palloc0(sizeof(PgFdwExecutionState));
-	node->fdw_state = (void *) festate;
+	fsstate = (PgFdwScanState *) palloc0(sizeof(PgFdwScanState));
+	node->fdw_state = (void *) fsstate;
 
 	/*
 	 * Identify which user to do the remote access as.	This should match what
@@ -630,8 +771,8 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
 
 	/* Get info about foreign table. */
-	festate->rel = node->ss.ss_currentRelation;
-	table = GetForeignTable(RelationGetRelid(festate->rel));
+	fsstate->rel = node->ss.ss_currentRelation;
+	table = GetForeignTable(RelationGetRelid(fsstate->rel));
 	server = GetForeignServer(table->serverid);
 	user = GetUserMapping(userid, server->serverid);
 
@@ -639,29 +780,29 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 	 * Get connection to the foreign server.  Connection manager will
 	 * establish new connection if necessary.
 	 */
-	festate->conn = GetConnection(server, user);
+	fsstate->conn = GetConnection(server, user, false);
 
 	/* Assign a unique ID for my cursor */
-	festate->cursor_number = GetCursorNumber(festate->conn);
-	festate->cursor_exists = false;
+	fsstate->cursor_number = GetCursorNumber(fsstate->conn);
+	fsstate->cursor_exists = false;
 
 	/* Get private info created by planner functions. */
-	festate->fdw_private = fsplan->fdw_private;
+	fsstate->fdw_private = fsplan->fdw_private;
 
 	/* Create contexts for batches of tuples and per-tuple temp workspace. */
-	festate->batch_cxt = AllocSetContextCreate(estate->es_query_cxt,
+	fsstate->batch_cxt = AllocSetContextCreate(estate->es_query_cxt,
 											   "postgres_fdw tuple data",
 											   ALLOCSET_DEFAULT_MINSIZE,
 											   ALLOCSET_DEFAULT_INITSIZE,
 											   ALLOCSET_DEFAULT_MAXSIZE);
-	festate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt,
+	fsstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt,
 											  "postgres_fdw temporary data",
 											  ALLOCSET_SMALL_MINSIZE,
 											  ALLOCSET_SMALL_INITSIZE,
 											  ALLOCSET_SMALL_MAXSIZE);
 
 	/* Get info we'll need for data conversion. */
-	festate->attinmeta = TupleDescGetAttInMetadata(RelationGetDescr(festate->rel));
+	fsstate->attinmeta = TupleDescGetAttInMetadata(RelationGetDescr(fsstate->rel));
 
 	/*
 	 * Allocate buffer for query parameters, if the remote conditions use any.
@@ -673,7 +814,7 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 	 * null values that are arbitrarily marked as being of type int4.
 	 */
 	param_numbers = (List *)
-		list_nth(festate->fdw_private, FdwPrivateExternParamIds);
+		list_nth(fsstate->fdw_private, FdwScanPrivateExternParamIds);
 	if (param_numbers != NIL)
 	{
 		ParamListInfo params = estate->es_param_list_info;
@@ -682,21 +823,21 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 	}
 	else
 		numParams = 0;
-	festate->numParams = numParams;
+	fsstate->numParams = numParams;
 	if (numParams > 0)
 	{
 		/* we initially fill all slots with value = NULL, type = int4 */
-		festate->param_types = (Oid *) palloc(numParams * sizeof(Oid));
-		festate->param_values = (const char **) palloc0(numParams * sizeof(char *));
+		fsstate->param_types = (Oid *) palloc(numParams * sizeof(Oid));
+		fsstate->param_values = (const char **) palloc0(numParams * sizeof(char *));
 		for (i = 0; i < numParams; i++)
-			festate->param_types[i] = INT4OID;
+			fsstate->param_types[i] = INT4OID;
 	}
 	else
 	{
-		festate->param_types = NULL;
-		festate->param_values = NULL;
+		fsstate->param_types = NULL;
+		fsstate->param_values = NULL;
 	}
-	festate->extparams_done = false;
+	fsstate->extparams_done = false;
 }
 
 /*
@@ -707,33 +848,33 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 static TupleTableSlot *
 postgresIterateForeignScan(ForeignScanState *node)
 {
-	PgFdwExecutionState *festate = (PgFdwExecutionState *) node->fdw_state;
+	PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state;
 	TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
 
 	/*
 	 * If this is the first call after Begin or ReScan, we need to create the
 	 * cursor on the remote side.
 	 */
-	if (!festate->cursor_exists)
+	if (!fsstate->cursor_exists)
 		create_cursor(node);
 
 	/*
 	 * Get some more tuples, if we've run out.
 	 */
-	if (festate->next_tuple >= festate->num_tuples)
+	if (fsstate->next_tuple >= fsstate->num_tuples)
 	{
 		/* No point in another fetch if we already detected EOF, though. */
-		if (!festate->eof_reached)
+		if (!fsstate->eof_reached)
 			fetch_more_data(node);
 		/* If we didn't get any tuples, must be end of data. */
-		if (festate->next_tuple >= festate->num_tuples)
+		if (fsstate->next_tuple >= fsstate->num_tuples)
 			return ExecClearTuple(slot);
 	}
 
 	/*
 	 * Return the next tuple.
 	 */
-	ExecStoreTuple(festate->tuples[festate->next_tuple++],
+	ExecStoreTuple(fsstate->tuples[fsstate->next_tuple++],
 				   slot,
 				   InvalidBuffer,
 				   false);
@@ -748,7 +889,7 @@ postgresIterateForeignScan(ForeignScanState *node)
 static void
 postgresReScanForeignScan(ForeignScanState *node)
 {
-	PgFdwExecutionState *festate = (PgFdwExecutionState *) node->fdw_state;
+	PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state;
 	char		sql[64];
 	PGresult   *res;
 
@@ -758,7 +899,7 @@ postgresReScanForeignScan(ForeignScanState *node)
 	 */
 
 	/* If we haven't created the cursor yet, nothing to do. */
-	if (!festate->cursor_exists)
+	if (!fsstate->cursor_exists)
 		return;
 
 	/*
@@ -769,19 +910,19 @@ postgresReScanForeignScan(ForeignScanState *node)
 	 */
 	if (node->ss.ps.chgParam != NULL)
 	{
-		festate->cursor_exists = false;
+		fsstate->cursor_exists = false;
 		snprintf(sql, sizeof(sql), "CLOSE c%u",
-				 festate->cursor_number);
+				 fsstate->cursor_number);
 	}
-	else if (festate->fetch_ct_2 > 1)
+	else if (fsstate->fetch_ct_2 > 1)
 	{
 		snprintf(sql, sizeof(sql), "MOVE BACKWARD ALL IN c%u",
-				 festate->cursor_number);
+				 fsstate->cursor_number);
 	}
 	else
 	{
 		/* Easy: just rescan what we already have in memory, if anything */
-		festate->next_tuple = 0;
+		fsstate->next_tuple = 0;
 		return;
 	}
 
@@ -789,17 +930,17 @@ postgresReScanForeignScan(ForeignScanState *node)
 	 * We don't use a PG_TRY block here, so be careful not to throw error
 	 * without releasing the PGresult.
 	 */
-	res = PQexec(festate->conn, sql);
+	res = PQexec(fsstate->conn, sql);
 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
 		pgfdw_report_error(ERROR, res, true, sql);
 	PQclear(res);
 
 	/* Now force a fresh FETCH. */
-	festate->tuples = NULL;
-	festate->num_tuples = 0;
-	festate->next_tuple = 0;
-	festate->fetch_ct_2 = 0;
-	festate->eof_reached = false;
+	fsstate->tuples = NULL;
+	fsstate->num_tuples = 0;
+	fsstate->next_tuple = 0;
+	fsstate->fetch_ct_2 = 0;
+	fsstate->eof_reached = false;
 }
 
 /*
@@ -809,23 +950,529 @@ postgresReScanForeignScan(ForeignScanState *node)
 static void
 postgresEndForeignScan(ForeignScanState *node)
 {
-	PgFdwExecutionState *festate = (PgFdwExecutionState *) node->fdw_state;
+	PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state;
 
-	/* if festate is NULL, we are in EXPLAIN; nothing to do */
-	if (festate == NULL)
+	/* if fsstate is NULL, we are in EXPLAIN; nothing to do */
+	if (fsstate == NULL)
 		return;
 
 	/* Close the cursor if open, to prevent accumulation of cursors */
-	if (festate->cursor_exists)
-		close_cursor(festate->conn, festate->cursor_number);
+	if (fsstate->cursor_exists)
+		close_cursor(fsstate->conn, fsstate->cursor_number);
 
 	/* Release remote connection */
-	ReleaseConnection(festate->conn);
-	festate->conn = NULL;
+	ReleaseConnection(fsstate->conn);
+	fsstate->conn = NULL;
 
 	/* MemoryContexts will be deleted automatically. */
 }
 
+/*
+ * postgresAddForeignUpdateTargets
+ *		Add resjunk column(s) needed for update/delete on a foreign table
+ */
+static void
+postgresAddForeignUpdateTargets(Query *parsetree,
+								RangeTblEntry *target_rte,
+								Relation target_relation)
+{
+	Var		   *var;
+	const char *attrname;
+	TargetEntry *tle;
+
+	/*
+	 * In postgres_fdw, what we need is the ctid, same as for a regular table.
+	 */
+
+	/* Make a Var representing the desired value */
+	var = makeVar(parsetree->resultRelation,
+				  SelfItemPointerAttributeNumber,
+				  TIDOID,
+				  -1,
+				  InvalidOid,
+				  0);
+
+	/* Wrap it in a resjunk TLE with the right name ... */
+	attrname = "ctid";
+
+	tle = makeTargetEntry((Expr *) var,
+						  list_length(parsetree->targetList) + 1,
+						  pstrdup(attrname),
+						  true);
+
+	/* ... and add it to the query's targetlist */
+	parsetree->targetList = lappend(parsetree->targetList, tle);
+}
+
+/*
+ * postgresPlanForeignModify
+ *		Plan an insert/update/delete operation on a foreign table
+ *
+ * Note: currently, the plan tree generated for UPDATE/DELETE will always
+ * include a ForeignScan that retrieves ctids (using SELECT FOR UPDATE)
+ * and then the ModifyTable node will have to execute individual remote
+ * UPDATE/DELETE commands.	If there are no local conditions or joins
+ * needed, it'd be better to let the scan node do UPDATE/DELETE RETURNING
+ * and then do nothing at ModifyTable.	Room for future optimization ...
+ */
+static List *
+postgresPlanForeignModify(PlannerInfo *root,
+						  ModifyTable *plan,
+						  Index resultRelation,
+						  int subplan_index)
+{
+	CmdType		operation = plan->operation;
+	StringInfoData sql;
+	List	   *targetAttrs = NIL;
+	List	   *returningList = NIL;
+
+	initStringInfo(&sql);
+
+	/*
+	 * Construct a list of the columns that are to be assigned during INSERT
+	 * or UPDATE.  We should transmit only these columns, for performance and
+	 * to respect any DEFAULT values the remote side may have for other
+	 * columns.  (XXX this will need some re-thinking when we support default
+	 * expressions for foreign tables.)
+	 */
+	if (operation == CMD_INSERT || operation == CMD_UPDATE)
+	{
+		RangeTblEntry *rte = planner_rt_fetch(resultRelation, root);
+		Bitmapset  *tmpset = bms_copy(rte->modifiedCols);
+		AttrNumber	col;
+
+		while ((col = bms_first_member(tmpset)) >= 0)
+		{
+			col += FirstLowInvalidHeapAttributeNumber;
+			if (col <= InvalidAttrNumber)		/* shouldn't happen */
+				elog(ERROR, "system-column update is not supported");
+			targetAttrs = lappend_int(targetAttrs, col);
+		}
+	}
+
+	/*
+	 * Extract the relevant RETURNING list if any.
+	 */
+	if (plan->returningLists)
+		returningList = (List *) list_nth(plan->returningLists, subplan_index);
+
+	/*
+	 * Construct the SQL command string.
+	 */
+	switch (operation)
+	{
+		case CMD_INSERT:
+			deparseInsertSql(&sql, root, resultRelation,
+							 targetAttrs, returningList);
+			break;
+		case CMD_UPDATE:
+			deparseUpdateSql(&sql, root, resultRelation,
+							 targetAttrs, returningList);
+			break;
+		case CMD_DELETE:
+			deparseDeleteSql(&sql, root, resultRelation, returningList);
+			break;
+		default:
+			elog(ERROR, "unexpected operation: %d", (int) operation);
+			break;
+	}
+
+	/*
+	 * Build the fdw_private list that will be available to the executor.
+	 * Items in the list must match enum FdwModifyPrivateIndex, above.
+	 */
+	return list_make3(makeString(sql.data),
+					  targetAttrs,
+					  makeInteger((returningList != NIL)));
+}
+
+/*
+ * postgresBeginForeignModify
+ *		Begin an insert/update/delete operation on a foreign table
+ */
+static void
+postgresBeginForeignModify(ModifyTableState *mtstate,
+						   ResultRelInfo *resultRelInfo,
+						   List *fdw_private,
+						   int subplan_index,
+						   int eflags)
+{
+	PgFdwModifyState *fmstate;
+	EState	   *estate = mtstate->ps.state;
+	CmdType		operation = mtstate->operation;
+	Relation	rel = resultRelInfo->ri_RelationDesc;
+	RangeTblEntry *rte;
+	Oid			userid;
+	ForeignTable *table;
+	ForeignServer *server;
+	UserMapping *user;
+	AttrNumber	n_params;
+	Oid			typefnoid;
+	bool		isvarlena;
+	ListCell   *lc;
+
+	/*
+	 * Do nothing in EXPLAIN (no ANALYZE) case.  resultRelInfo->ri_FdwState
+	 * stays NULL.
+	 */
+	if (eflags & EXEC_FLAG_EXPLAIN_ONLY)
+		return;
+
+	/* Begin constructing PgFdwModifyState. */
+	fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
+	fmstate->rel = rel;
+
+	/*
+	 * Identify which user to do the remote access as.	This should match what
+	 * ExecCheckRTEPerms() does.
+	 */
+	rte = rt_fetch(resultRelInfo->ri_RangeTableIndex, estate->es_range_table);
+	userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
+
+	/* Get info about foreign table. */
+	table = GetForeignTable(RelationGetRelid(rel));
+	server = GetForeignServer(table->serverid);
+	user = GetUserMapping(userid, server->serverid);
+
+	/* Open connection; report that we'll create a prepared statement. */
+	fmstate->conn = GetConnection(server, user, true);
+	fmstate->p_name = NULL;		/* prepared statement not made yet */
+
+	/* Deconstruct fdw_private data. */
+	fmstate->query = strVal(list_nth(fdw_private,
+									 FdwModifyPrivateUpdateSql));
+	fmstate->target_attrs = (List *) list_nth(fdw_private,
+											  FdwModifyPrivateTargetAttnums);
+	fmstate->has_returning = intVal(list_nth(fdw_private,
+											 FdwModifyPrivateHasReturning));
+
+	/* Create context for per-tuple temp workspace. */
+	fmstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt,
+											  "postgres_fdw temporary data",
+											  ALLOCSET_SMALL_MINSIZE,
+											  ALLOCSET_SMALL_INITSIZE,
+											  ALLOCSET_SMALL_MAXSIZE);
+
+	/* Prepare for input conversion of RETURNING results. */
+	if (fmstate->has_returning)
+		fmstate->attinmeta = TupleDescGetAttInMetadata(RelationGetDescr(rel));
+
+	/* Prepare for output conversion of parameters used in prepared stmt. */
+	n_params = list_length(fmstate->target_attrs) + 1;
+	fmstate->p_flinfo = (FmgrInfo *) palloc0(sizeof(FmgrInfo) * n_params);
+	fmstate->p_nums = 0;
+
+	if (operation == CMD_UPDATE || operation == CMD_DELETE)
+	{
+		/* Find the ctid resjunk column in the subplan's result */
+		Plan	   *subplan = mtstate->mt_plans[subplan_index]->plan;
+
+		fmstate->ctidAttno = ExecFindJunkAttributeInTlist(subplan->targetlist,
+														  "ctid");
+		if (!AttributeNumberIsValid(fmstate->ctidAttno))
+			elog(ERROR, "could not find junk ctid column");
+
+		/* First transmittable parameter will be ctid */
+		getTypeOutputInfo(TIDOID, &typefnoid, &isvarlena);
+		fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
+		fmstate->p_nums++;
+	}
+
+	if (operation == CMD_INSERT || operation == CMD_UPDATE)
+	{
+		/* Set up for remaining transmittable parameters */
+		foreach(lc, fmstate->target_attrs)
+		{
+			int			attnum = lfirst_int(lc);
+			Form_pg_attribute attr = RelationGetDescr(rel)->attrs[attnum - 1];
+
+			Assert(!attr->attisdropped);
+
+			getTypeOutputInfo(attr->atttypid, &typefnoid, &isvarlena);
+			fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
+			fmstate->p_nums++;
+		}
+	}
+
+	Assert(fmstate->p_nums <= n_params);
+
+	resultRelInfo->ri_FdwState = fmstate;
+}
+
+/*
+ * postgresExecForeignInsert
+ *		Insert one row into a foreign table
+ */
+static TupleTableSlot *
+postgresExecForeignInsert(EState *estate,
+						  ResultRelInfo *resultRelInfo,
+						  TupleTableSlot *slot,
+						  TupleTableSlot *planSlot)
+{
+	PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState;
+	const char **p_values;
+	PGresult   *res;
+	int			n_rows;
+
+	/* Set up the prepared statement on the remote server, if we didn't yet */
+	if (!fmstate->p_name)
+		prepare_foreign_modify(fmstate);
+
+	/* Convert parameters needed by prepared statement to text form */
+	p_values = convert_prep_stmt_params(fmstate, NULL, slot);
+
+	/*
+	 * Execute the prepared statement, and check for success.
+	 *
+	 * We don't use a PG_TRY block here, so be careful not to throw error
+	 * without releasing the PGresult.
+	 */
+	res = PQexecPrepared(fmstate->conn,
+						 fmstate->p_name,
+						 fmstate->p_nums,
+						 p_values,
+						 NULL,
+						 NULL,
+						 0);
+	if (PQresultStatus(res) !=
+		(fmstate->has_returning ? PGRES_TUPLES_OK : PGRES_COMMAND_OK))
+		pgfdw_report_error(ERROR, res, true, fmstate->query);
+
+	/* Check number of rows affected, and fetch RETURNING tuple if any */
+	if (fmstate->has_returning)
+	{
+		n_rows = PQntuples(res);
+		if (n_rows > 0)
+			store_returning_result(fmstate, slot, res);
+	}
+	else
+		n_rows = atoi(PQcmdTuples(res));
+
+	/* And clean up */
+	PQclear(res);
+
+	MemoryContextReset(fmstate->temp_cxt);
+
+	/* Return NULL if nothing was inserted on the remote end */
+	return (n_rows > 0) ? slot : NULL;
+}
+
+/*
+ * postgresExecForeignUpdate
+ *		Update one row in a foreign table
+ */
+static TupleTableSlot *
+postgresExecForeignUpdate(EState *estate,
+						  ResultRelInfo *resultRelInfo,
+						  TupleTableSlot *slot,
+						  TupleTableSlot *planSlot)
+{
+	PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState;
+	Datum		datum;
+	bool		isNull;
+	const char **p_values;
+	PGresult   *res;
+	int			n_rows;
+
+	/* Set up the prepared statement on the remote server, if we didn't yet */
+	if (!fmstate->p_name)
+		prepare_foreign_modify(fmstate);
+
+	/* Get the ctid that was passed up as a resjunk column */
+	datum = ExecGetJunkAttribute(planSlot,
+								 fmstate->ctidAttno,
+								 &isNull);
+	/* shouldn't ever get a null result... */
+	if (isNull)
+		elog(ERROR, "ctid is NULL");
+
+	/* Convert parameters needed by prepared statement to text form */
+	p_values = convert_prep_stmt_params(fmstate,
+										(ItemPointer) DatumGetPointer(datum),
+										slot);
+
+	/*
+	 * Execute the prepared statement, and check for success.
+	 *
+	 * We don't use a PG_TRY block here, so be careful not to throw error
+	 * without releasing the PGresult.
+	 */
+	res = PQexecPrepared(fmstate->conn,
+						 fmstate->p_name,
+						 fmstate->p_nums,
+						 p_values,
+						 NULL,
+						 NULL,
+						 0);
+	if (PQresultStatus(res) !=
+		(fmstate->has_returning ? PGRES_TUPLES_OK : PGRES_COMMAND_OK))
+		pgfdw_report_error(ERROR, res, true, fmstate->query);
+
+	/* Check number of rows affected, and fetch RETURNING tuple if any */
+	if (fmstate->has_returning)
+	{
+		n_rows = PQntuples(res);
+		if (n_rows > 0)
+			store_returning_result(fmstate, slot, res);
+	}
+	else
+		n_rows = atoi(PQcmdTuples(res));
+
+	/* And clean up */
+	PQclear(res);
+
+	MemoryContextReset(fmstate->temp_cxt);
+
+	/* Return NULL if nothing was updated on the remote end */
+	return (n_rows > 0) ? slot : NULL;
+}
+
+/*
+ * postgresExecForeignDelete
+ *		Delete one row from a foreign table
+ */
+static TupleTableSlot *
+postgresExecForeignDelete(EState *estate,
+						  ResultRelInfo *resultRelInfo,
+						  TupleTableSlot *slot,
+						  TupleTableSlot *planSlot)
+{
+	PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState;
+	Datum		datum;
+	bool		isNull;
+	const char **p_values;
+	PGresult   *res;
+	int			n_rows;
+
+	/* Set up the prepared statement on the remote server, if we didn't yet */
+	if (!fmstate->p_name)
+		prepare_foreign_modify(fmstate);
+
+	/* Get the ctid that was passed up as a resjunk column */
+	datum = ExecGetJunkAttribute(planSlot,
+								 fmstate->ctidAttno,
+								 &isNull);
+	/* shouldn't ever get a null result... */
+	if (isNull)
+		elog(ERROR, "ctid is NULL");
+
+	/* Convert parameters needed by prepared statement to text form */
+	p_values = convert_prep_stmt_params(fmstate,
+										(ItemPointer) DatumGetPointer(datum),
+										NULL);
+
+	/*
+	 * Execute the prepared statement, and check for success.
+	 *
+	 * We don't use a PG_TRY block here, so be careful not to throw error
+	 * without releasing the PGresult.
+	 */
+	res = PQexecPrepared(fmstate->conn,
+						 fmstate->p_name,
+						 fmstate->p_nums,
+						 p_values,
+						 NULL,
+						 NULL,
+						 0);
+	if (PQresultStatus(res) !=
+		(fmstate->has_returning ? PGRES_TUPLES_OK : PGRES_COMMAND_OK))
+		pgfdw_report_error(ERROR, res, true, fmstate->query);
+
+	/* Check number of rows affected, and fetch RETURNING tuple if any */
+	if (fmstate->has_returning)
+	{
+		n_rows = PQntuples(res);
+		if (n_rows > 0)
+			store_returning_result(fmstate, slot, res);
+	}
+	else
+		n_rows = atoi(PQcmdTuples(res));
+
+	/* And clean up */
+	PQclear(res);
+
+	MemoryContextReset(fmstate->temp_cxt);
+
+	/* Return NULL if nothing was deleted on the remote end */
+	return (n_rows > 0) ? slot : NULL;
+}
+
+/*
+ * postgresEndForeignModify
+ *		Finish an insert/update/delete operation on a foreign table
+ */
+static void
+postgresEndForeignModify(EState *estate,
+						 ResultRelInfo *resultRelInfo)
+{
+	PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState;
+
+	/* If fmstate is NULL, we are in EXPLAIN; nothing to do */
+	if (fmstate == NULL)
+		return;
+
+	/* If we created a prepared statement, destroy it */
+	if (fmstate->p_name)
+	{
+		char		sql[64];
+		PGresult   *res;
+
+		snprintf(sql, sizeof(sql), "DEALLOCATE %s", fmstate->p_name);
+
+		/*
+		 * We don't use a PG_TRY block here, so be careful not to throw error
+		 * without releasing the PGresult.
+		 */
+		res = PQexec(fmstate->conn, sql);
+		if (PQresultStatus(res) != PGRES_COMMAND_OK)
+			pgfdw_report_error(ERROR, res, true, sql);
+		PQclear(res);
+		fmstate->p_name = NULL;
+	}
+
+	/* Release remote connection */
+	ReleaseConnection(fmstate->conn);
+	fmstate->conn = NULL;
+}
+
+/*
+ * postgresExplainForeignScan
+ *		Produce extra output for EXPLAIN of a ForeignScan on a foreign table
+ */
+static void
+postgresExplainForeignScan(ForeignScanState *node, ExplainState *es)
+{
+	List	   *fdw_private;
+	char	   *sql;
+
+	if (es->verbose)
+	{
+		fdw_private = ((ForeignScan *) node->ss.ps.plan)->fdw_private;
+		sql = strVal(list_nth(fdw_private, FdwScanPrivateSelectSql));
+		ExplainPropertyText("Remote SQL", sql, es);
+	}
+}
+
+/*
+ * postgresExplainForeignModify
+ *		Produce extra output for EXPLAIN of a ModifyTable on a foreign table
+ */
+static void
+postgresExplainForeignModify(ModifyTableState *mtstate,
+							 ResultRelInfo *rinfo,
+							 List *fdw_private,
+							 int subplan_index,
+							 ExplainState *es)
+{
+	if (es->verbose)
+	{
+		char	   *sql = strVal(list_nth(fdw_private,
+										  FdwModifyPrivateUpdateSql));
+
+		ExplainPropertyText("Remote SQL", sql, es);
+	}
+}
+
 /*
  * Estimate costs of executing given SQL statement.
  */
@@ -885,11 +1532,11 @@ get_remote_estimate(const char *sql, PGconn *conn,
 static void
 create_cursor(ForeignScanState *node)
 {
-	PgFdwExecutionState *festate = (PgFdwExecutionState *) node->fdw_state;
-	int			numParams = festate->numParams;
-	Oid		   *types = festate->param_types;
-	const char **values = festate->param_values;
-	PGconn	   *conn = festate->conn;
+	PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state;
+	int			numParams = fsstate->numParams;
+	Oid		   *types = fsstate->param_types;
+	const char **values = fsstate->param_values;
+	PGconn	   *conn = fsstate->conn;
 	char	   *sql;
 	StringInfoData buf;
 	PGresult   *res;
@@ -904,14 +1551,14 @@ create_cursor(ForeignScanState *node)
 	 * recreate the cursor after a rescan, so we could need to re-use the
 	 * values anyway.
 	 */
-	if (numParams > 0 && !festate->extparams_done)
+	if (numParams > 0 && !fsstate->extparams_done)
 	{
 		ParamListInfo params = node->ss.ps.state->es_param_list_info;
 		List	   *param_numbers;
 		ListCell   *lc;
 
 		param_numbers = (List *)
-			list_nth(festate->fdw_private, FdwPrivateExternParamIds);
+			list_nth(fsstate->fdw_private, FdwScanPrivateExternParamIds);
 		foreach(lc, param_numbers)
 		{
 			int			paramno = lfirst_int(lc);
@@ -929,8 +1576,8 @@ create_cursor(ForeignScanState *node)
 			 * 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
+			 * 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;
@@ -951,14 +1598,14 @@ create_cursor(ForeignScanState *node)
 															prm->value);
 			}
 		}
-		festate->extparams_done = true;
+		fsstate->extparams_done = true;
 	}
 
 	/* Construct the DECLARE CURSOR command */
-	sql = strVal(list_nth(festate->fdw_private, FdwPrivateSelectSql));
+	sql = strVal(list_nth(fsstate->fdw_private, FdwScanPrivateSelectSql));
 	initStringInfo(&buf);
 	appendStringInfo(&buf, "DECLARE c%u CURSOR FOR\n%s",
-					 festate->cursor_number, sql);
+					 fsstate->cursor_number, sql);
 
 	/*
 	 * We don't use a PG_TRY block here, so be careful not to throw error
@@ -971,12 +1618,12 @@ create_cursor(ForeignScanState *node)
 	PQclear(res);
 
 	/* Mark the cursor as created, and show no tuples have been retrieved */
-	festate->cursor_exists = true;
-	festate->tuples = NULL;
-	festate->num_tuples = 0;
-	festate->next_tuple = 0;
-	festate->fetch_ct_2 = 0;
-	festate->eof_reached = false;
+	fsstate->cursor_exists = true;
+	fsstate->tuples = NULL;
+	fsstate->num_tuples = 0;
+	fsstate->next_tuple = 0;
+	fsstate->fetch_ct_2 = 0;
+	fsstate->eof_reached = false;
 
 	/* Clean up */
 	pfree(buf.data);
@@ -988,7 +1635,7 @@ create_cursor(ForeignScanState *node)
 static void
 fetch_more_data(ForeignScanState *node)
 {
-	PgFdwExecutionState *festate = (PgFdwExecutionState *) node->fdw_state;
+	PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state;
 	PGresult   *volatile res = NULL;
 	MemoryContext oldcontext;
 
@@ -996,14 +1643,14 @@ fetch_more_data(ForeignScanState *node)
 	 * We'll store the tuples in the batch_cxt.  First, flush the previous
 	 * batch.
 	 */
-	festate->tuples = NULL;
-	MemoryContextReset(festate->batch_cxt);
-	oldcontext = MemoryContextSwitchTo(festate->batch_cxt);
+	fsstate->tuples = NULL;
+	MemoryContextReset(fsstate->batch_cxt);
+	oldcontext = MemoryContextSwitchTo(fsstate->batch_cxt);
 
 	/* PGresult must be released before leaving this function. */
 	PG_TRY();
 	{
-		PGconn	   *conn = festate->conn;
+		PGconn	   *conn = fsstate->conn;
 		char		sql[64];
 		int			fetch_size;
 		int			numrows;
@@ -1013,36 +1660,36 @@ fetch_more_data(ForeignScanState *node)
 		fetch_size = 100;
 
 		snprintf(sql, sizeof(sql), "FETCH %d FROM c%u",
-				 fetch_size, festate->cursor_number);
+				 fetch_size, fsstate->cursor_number);
 
 		res = PQexec(conn, sql);
 		/* On error, report the original query, not the FETCH. */
 		if (PQresultStatus(res) != PGRES_TUPLES_OK)
 			pgfdw_report_error(ERROR, res, false,
-							   strVal(list_nth(festate->fdw_private,
-											   FdwPrivateSelectSql)));
+							   strVal(list_nth(fsstate->fdw_private,
+											   FdwScanPrivateSelectSql)));
 
 		/* Convert the data into HeapTuples */
 		numrows = PQntuples(res);
-		festate->tuples = (HeapTuple *) palloc0(numrows * sizeof(HeapTuple));
-		festate->num_tuples = numrows;
-		festate->next_tuple = 0;
+		fsstate->tuples = (HeapTuple *) palloc0(numrows * sizeof(HeapTuple));
+		fsstate->num_tuples = numrows;
+		fsstate->next_tuple = 0;
 
 		for (i = 0; i < numrows; i++)
 		{
-			festate->tuples[i] =
+			fsstate->tuples[i] =
 				make_tuple_from_result_row(res, i,
-										   festate->rel,
-										   festate->attinmeta,
-										   festate->temp_cxt);
+										   fsstate->rel,
+										   fsstate->attinmeta,
+										   fsstate->temp_cxt);
 		}
 
 		/* Update fetch_ct_2 */
-		if (festate->fetch_ct_2 < 2)
-			festate->fetch_ct_2++;
+		if (fsstate->fetch_ct_2 < 2)
+			fsstate->fetch_ct_2++;
 
 		/* Must be EOF if we didn't get as many tuples as we asked for. */
-		festate->eof_reached = (numrows < fetch_size);
+		fsstate->eof_reached = (numrows < fetch_size);
 
 		PQclear(res);
 		res = NULL;
@@ -1079,6 +1726,136 @@ close_cursor(PGconn *conn, unsigned int cursor_number)
 	PQclear(res);
 }
 
+/*
+ * prepare_foreign_modify
+ *		Establish a prepared statement for execution of INSERT/UPDATE/DELETE
+ */
+static void
+prepare_foreign_modify(PgFdwModifyState *fmstate)
+{
+	char		prep_name[NAMEDATALEN];
+	char	   *p_name;
+	PGresult   *res;
+
+	/* Construct name we'll use for the prepared statement. */
+	snprintf(prep_name, sizeof(prep_name), "pgsql_fdw_prep_%u",
+			 GetPrepStmtNumber(fmstate->conn));
+	p_name = pstrdup(prep_name);
+
+	/*
+	 * We intentionally do not specify parameter types here, but leave the
+	 * remote server to derive them by default.  This avoids possible problems
+	 * with the remote server using different type OIDs than we do.  All of
+	 * the prepared statements we use in this module are simple enough that
+	 * the remote server will make the right choices.
+	 *
+	 * We don't use a PG_TRY block here, so be careful not to throw error
+	 * without releasing the PGresult.
+	 */
+	res = PQprepare(fmstate->conn,
+					p_name,
+					fmstate->query,
+					0,
+					NULL);
+
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		pgfdw_report_error(ERROR, res, true, fmstate->query);
+	PQclear(res);
+
+	/* This action shows that the prepare has been done. */
+	fmstate->p_name = p_name;
+}
+
+/*
+ * convert_prep_stmt_params
+ *		Create array of text strings representing parameter values
+ *
+ * tupleid is ctid to send, or NULL if none
+ * slot is slot to get remaining parameters from, or NULL if none
+ *
+ * Data is constructed in temp_cxt; caller should reset that after use.
+ */
+static const char **
+convert_prep_stmt_params(PgFdwModifyState *fmstate,
+						 ItemPointer tupleid,
+						 TupleTableSlot *slot)
+{
+	const char **p_values;
+	int			pindex = 0;
+	MemoryContext oldcontext;
+
+	oldcontext = MemoryContextSwitchTo(fmstate->temp_cxt);
+
+	p_values = (const char **) palloc(sizeof(char *) * fmstate->p_nums);
+
+	/* 1st parameter should be ctid, if it's in use */
+	if (tupleid != NULL)
+	{
+		p_values[pindex] = OutputFunctionCall(&fmstate->p_flinfo[pindex],
+											  PointerGetDatum(tupleid));
+		pindex++;
+	}
+
+	/* get following parameters from slot */
+	if (slot != NULL)
+	{
+		ListCell   *lc;
+
+		foreach(lc, fmstate->target_attrs)
+		{
+			int			attnum = lfirst_int(lc);
+			Datum		value;
+			bool		isnull;
+
+			value = slot_getattr(slot, attnum, &isnull);
+			if (isnull)
+				p_values[pindex] = NULL;
+			else
+				p_values[pindex] = OutputFunctionCall(&fmstate->p_flinfo[pindex],
+													  value);
+			pindex++;
+		}
+	}
+
+	Assert(pindex == fmstate->p_nums);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return p_values;
+}
+
+/*
+ * store_returning_result
+ *		Store the result of a RETURNING clause
+ *
+ * On error, be sure to release the PGresult on the way out.  Callers do not
+ * have PG_TRY blocks to ensure this happens.
+ */
+static void
+store_returning_result(PgFdwModifyState *fmstate,
+					   TupleTableSlot *slot, PGresult *res)
+{
+	/* PGresult must be released before leaving this function. */
+	PG_TRY();
+	{
+		HeapTuple	newtup;
+
+		newtup = make_tuple_from_result_row(res, 0,
+											fmstate->rel,
+											fmstate->attinmeta,
+											fmstate->temp_cxt);
+		/* tuple will be deleted when it is cleared from the slot */
+		ExecStoreTuple(newtup, slot, InvalidBuffer, true);
+	}
+	PG_CATCH();
+	{
+		if (res)
+			PQclear(res);
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+}
+
 /*
  * postgresAnalyzeForeignTable
  *		Test whether analyzing this foreign table is supported
@@ -1099,7 +1876,7 @@ postgresAnalyzeForeignTable(Relation relation,
 	*func = postgresAcquireSampleRowsFunc;
 
 	/*
-	 * Now we have to get the number of pages.  It's annoying that the ANALYZE
+	 * Now we have to get the number of pages.	It's annoying that the ANALYZE
 	 * API requires us to return that now, because it forces some duplication
 	 * of effort between this routine and postgresAcquireSampleRowsFunc.  But
 	 * it's probably not worth redefining that API at this point.
@@ -1112,7 +1889,7 @@ postgresAnalyzeForeignTable(Relation relation,
 	table = GetForeignTable(RelationGetRelid(relation));
 	server = GetForeignServer(table->serverid);
 	user = GetUserMapping(relation->rd_rel->relowner, server->serverid);
-	conn = GetConnection(server, user);
+	conn = GetConnection(server, user, false);
 
 	/*
 	 * Construct command to get page count for relation.
@@ -1204,7 +1981,7 @@ postgresAcquireSampleRowsFunc(Relation relation, int elevel,
 	table = GetForeignTable(RelationGetRelid(relation));
 	server = GetForeignServer(table->serverid);
 	user = GetUserMapping(relation->rd_rel->relowner, server->serverid);
-	conn = GetConnection(server, user);
+	conn = GetConnection(server, user, false);
 
 	/*
 	 * Construct cursor that retrieves whole rows from remote.
@@ -1382,6 +2159,7 @@ make_tuple_from_result_row(PGresult *res,
 	Form_pg_attribute *attrs = tupdesc->attrs;
 	Datum	   *values;
 	bool	   *nulls;
+	ItemPointer ctid = NULL;
 	ConversionLocation errpos;
 	ErrorContextCallback errcallback;
 	MemoryContext oldcontext;
@@ -1449,6 +2227,21 @@ make_tuple_from_result_row(PGresult *res,
 		j++;
 	}
 
+	/*
+	 * Convert ctid if present.  XXX we could stand to have a cleaner way of
+	 * detecting whether ctid is included in the result.
+	 */
+	if (j < PQnfields(res))
+	{
+		char	   *valstr;
+		Datum		datum;
+
+		valstr = PQgetvalue(res, row, j);
+		datum = DirectFunctionCall1(tidin, CStringGetDatum(valstr));
+		ctid = (ItemPointer) DatumGetPointer(datum);
+		j++;
+	}
+
 	/* Uninstall error context callback. */
 	error_context_stack = errcallback.previous;
 
@@ -1463,6 +2256,9 @@ make_tuple_from_result_row(PGresult *res,
 
 	tuple = heap_form_tuple(tupdesc, values, nulls);
 
+	if (ctid)
+		tuple->t_self = *ctid;
+
 	/* Clean up */
 	MemoryContextReset(temp_context);
 
diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h
index 940b81a7398f9d1047bb775348035527b3291be3..236a60b4898090dc50920c434870209f07905045 100644
--- a/contrib/postgres_fdw/postgres_fdw.h
+++ b/contrib/postgres_fdw/postgres_fdw.h
@@ -21,9 +21,11 @@
 #include "libpq-fe.h"
 
 /* in connection.c */
-extern PGconn *GetConnection(ForeignServer *server, UserMapping *user);
+extern PGconn *GetConnection(ForeignServer *server, UserMapping *user,
+			  bool will_prep_stmt);
 extern void ReleaseConnection(PGconn *conn);
 extern unsigned int GetCursorNumber(PGconn *conn);
+extern unsigned int GetPrepStmtNumber(PGconn *conn);
 extern void pgfdw_report_error(int elevel, PGresult *res, bool clear,
 				   const char *sql);
 
@@ -39,14 +41,20 @@ extern void classifyConditions(PlannerInfo *root,
 				   List **param_conds,
 				   List **local_conds,
 				   List **param_numbers);
-extern void deparseSimpleSql(StringInfo buf,
+extern void deparseSelectSql(StringInfo buf,
 				 PlannerInfo *root,
 				 RelOptInfo *baserel,
-				 List *local_conds);
+				 Bitmapset *attrs_used);
 extern void appendWhereClause(StringInfo buf,
-				  bool has_where,
+				  PlannerInfo *root,
 				  List *exprs,
-				  PlannerInfo *root);
+				  bool is_first);
+extern void deparseInsertSql(StringInfo buf, PlannerInfo *root, Index rtindex,
+				 List *targetAttrs, List *returningList);
+extern void deparseUpdateSql(StringInfo buf, PlannerInfo *root, Index rtindex,
+				 List *targetAttrs, List *returningList);
+extern void deparseDeleteSql(StringInfo buf, PlannerInfo *root, Index rtindex,
+				 List *returningList);
 extern void deparseAnalyzeSizeSql(StringInfo buf, Relation rel);
 extern void deparseAnalyzeSql(StringInfo buf, Relation rel);
 
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 0b0231bc3063f0913ae6b74b7afa6c760a88632e..007109c7c768020ff09eb3569119325a3e883767 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -273,3 +273,77 @@ ROLLBACK TO s;
 FETCH c;
 SELECT * FROM ft1 ORDER BY c1 LIMIT 1;
 COMMIT;
+
+-- ===================================================================
+-- test writable foreign table stuff
+-- ===================================================================
+EXPLAIN (verbose, costs off)
+INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20;
+INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20;
+INSERT INTO ft2 (c1,c2,c3)
+  VALUES (1101,201,'aaa'), (1102,202,'bbb'), (1103,203,'ccc') RETURNING *;
+INSERT INTO ft2 (c1,c2,c3) VALUES (1104,204,'ddd'), (1105,205,'eee');
+UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3;
+UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *;
+EXPLAIN (verbose, costs off)
+UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9'
+  FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9;
+UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9'
+  FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9;
+DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING *;
+EXPLAIN (verbose, costs off)
+DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2;
+DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2;
+SELECT c1,c2,c3,c4 FROM ft2 ORDER BY c1;
+
+-- Test that defaults and triggers on remote table work as expected
+ALTER TABLE "S 1"."T 1" ALTER c6 SET DEFAULT '(^-^;)';
+CREATE OR REPLACE FUNCTION "S 1".F_BRTRIG() RETURNS trigger AS $$
+BEGIN
+    NEW.c3 = NEW.c3 || '_trig_update';
+    RETURN NEW;
+END;
+$$ LANGUAGE plpgsql;
+CREATE TRIGGER t1_br_insert BEFORE INSERT OR UPDATE
+    ON "S 1"."T 1" FOR EACH ROW EXECUTE PROCEDURE "S 1".F_BRTRIG();
+
+INSERT INTO ft2 (c1,c2,c3) VALUES (1208, 218, 'fff') RETURNING *;
+INSERT INTO ft2 (c1,c2,c3,c6) VALUES (1218, 218, 'ggg', '(--;') RETURNING *;
+UPDATE ft2 SET c2 = c2 + 600 WHERE c1 % 10 = 8 RETURNING *;
+
+-- Test errors thrown on remote side during update
+ALTER TABLE "S 1"."T 1" ADD CONSTRAINT c2positive CHECK (c2 >= 0);
+
+INSERT INTO ft1(c1, c2) VALUES(11, 12);  -- duplicate key
+INSERT INTO ft1(c1, c2) VALUES(1111, -2);  -- c2positive
+UPDATE ft1 SET c2 = -c2 WHERE c1 = 1;  -- c2positive
+
+-- Test savepoint/rollback behavior
+select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
+select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1;
+begin;
+update ft2 set c2 = 42 where c2 = 0;
+select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
+savepoint s1;
+update ft2 set c2 = 44 where c2 = 4;
+select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
+release savepoint s1;
+select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
+savepoint s2;
+update ft2 set c2 = 46 where c2 = 6;
+select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
+rollback to savepoint s2;
+select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
+release savepoint s2;
+select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
+savepoint s3;
+update ft2 set c2 = -2 where c2 = 42; -- fail on remote side
+rollback to savepoint s3;
+select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
+release savepoint s3;
+select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
+-- none of the above is committed yet remotely
+select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1;
+commit;
+select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
+select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1;
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 207de9b125994c2c08e3e4d9de4b9da62abc6686..e9135bffaa5a3847d264caca9f36e597d829578c 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -3040,36 +3040,41 @@ ANALYZE measurement;
     Foreign data is accessed with help from a
     <firstterm>foreign data wrapper</firstterm>. A foreign data wrapper is a
     library that can communicate with an external data source, hiding the
-    details of connecting to the data source and fetching data from it. There
-    is a foreign data wrapper available as a <filename>contrib</> module,
-    which can read plain data files residing on the server.  Other kind of
-    foreign data wrappers might be found as third party products.  If none of
-    the existing foreign data wrappers suit your needs, you can write your
-    own; see <xref linkend="fdwhandler">.
+    details of connecting to the data source and obtaining data from it.
+    There are some foreign data wrappers available as <filename>contrib</>
+    modules; see <xref linkend="contrib">.  Other kinds of foreign data
+    wrappers might be found as third party products.  If none of the existing
+    foreign data wrappers suit your needs, you can write your own; see <xref
+    linkend="fdwhandler">.
    </para>
 
    <para>
     To access foreign data, you need to create a <firstterm>foreign server</>
-    object, which defines how to connect to a particular external data source,
-    according to the set of options used by a particular foreign data
+    object, which defines how to connect to a particular external data source
+    according to the set of options used by its supporting foreign data
     wrapper. Then you need to create one or more <firstterm>foreign
     tables</firstterm>, which define the structure of the remote data. A
     foreign table can be used in queries just like a normal table, but a
     foreign table has no storage in the PostgreSQL server.  Whenever it is
     used, <productname>PostgreSQL</productname> asks the foreign data wrapper
-    to fetch the data from the external source.
+    to fetch data from the external source, or transmit data to the external
+    source in the case of update commands.
    </para>
 
    <para>
-    Accessing remote data may require authentication at the external
+    Accessing remote data may require authenticating to the external
     data source.  This information can be provided by a
-    <firstterm>user mapping</>, which can provide additional options based
+    <firstterm>user mapping</>, which can provide additional data
+    such as user names and passwords based
     on the current <productname>PostgreSQL</productname> role.
    </para>
 
    <para>
-    Currently, foreign tables are read-only.  This limitation may be fixed
-    in a future release.
+    For additional information, see
+    <xref linkend="sql-createforeigndatawrapper">,
+    <xref linkend="sql-createserver">,
+    <xref linkend="sql-createusermapping">, and
+    <xref linkend="sql-createforeigntable">.
    </para>
  </sect1>
 
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index 638b6ab9ce8a93a99202361d23b340fceb9ae2da..e6bce195e63bb544761f9356969246ceb7facb58 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -13,14 +13,15 @@
     wrapper, which consists of a set of functions that the core server
     calls.  The foreign data wrapper is responsible for fetching
     data from the remote data source and returning it to the
-    <productname>PostgreSQL</productname> executor. This chapter outlines how
-    to write a new foreign data wrapper.
+    <productname>PostgreSQL</productname> executor.  If updating foreign
+    tables is to be supported, the wrapper must handle that, too.
+    This chapter outlines how to write a new foreign data wrapper.
    </para>
 
    <para>
     The foreign data wrappers included in the standard distribution are good
     references when trying to write your own.  Look into the
-    <filename>contrib/file_fdw</> subdirectory of the source tree.
+    <filename>contrib</> subdirectory of the source tree.
     The <xref linkend="sql-createforeigndatawrapper"> reference page also has
     some useful details.
    </para>
@@ -84,9 +85,19 @@
 
     <para>
      The FDW handler function returns a palloc'd <structname>FdwRoutine</>
-     struct containing pointers to the following callback functions:
+     struct containing pointers to the callback functions described below.
+     The scan-related functions are required, the rest are optional.
     </para>
 
+    <para>
+     The <structname>FdwRoutine</> struct type is declared in
+     <filename>src/include/foreign/fdwapi.h</>, which see for additional
+     details.
+    </para>
+
+   <sect2 id="fdw-callbacks-scan">
+    <title>FDW Routines For Scanning Foreign Tables</title>
+
     <para>
 <programlisting>
 void
@@ -96,7 +107,7 @@ GetForeignRelSize (PlannerInfo *root,
 </programlisting>
 
      Obtain relation size estimates for a foreign table.  This is called
-     at the beginning of planning for a query involving a foreign table.
+     at the beginning of planning for a query that scans a foreign table.
      <literal>root</> is the planner's global information about the query;
      <literal>baserel</> is the planner's information about this table; and
      <literal>foreigntableid</> is the <structname>pg_class</> OID of the
@@ -181,23 +192,6 @@ GetForeignPlan (PlannerInfo *root,
     <para>
 <programlisting>
 void
-ExplainForeignScan (ForeignScanState *node,
-                    ExplainState *es);
-</programlisting>
-
-     Print additional <command>EXPLAIN</> output for a foreign table scan.
-     This can just return if there is no need to print anything.
-     Otherwise, it should call <function>ExplainPropertyText</> and
-     related functions to add fields to the <command>EXPLAIN</> output.
-     The flag fields in <literal>es</> can be used to determine what to
-     print, and the state of the <structname>ForeignScanState</> node
-     can be inspected to provide run-time statistics in the <command>EXPLAIN
-     ANALYZE</> case.
-    </para>
-
-    <para>
-<programlisting>
-void
 BeginForeignScan (ForeignScanState *node,
                   int eflags);
 </programlisting>
@@ -212,6 +206,8 @@ BeginForeignScan (ForeignScanState *node,
      <structname>ForeignScanState</> node (in particular, from the underlying
      <structname>ForeignScan</> plan node, which contains any FDW-private
      information provided by <function>GetForeignPlan</>).
+     <literal>eflags</> contains flag bits describing the executor's
+     operating mode for this plan node.
     </para>
 
     <para>
@@ -246,9 +242,9 @@ IterateForeignScan (ForeignScanState *node);
 
     <para>
      Note that <productname>PostgreSQL</productname>'s executor doesn't care
-     whether the rows returned violate the <literal>NOT NULL</literal>
-     constraints which were defined on the foreign table columns - but the
-     planner does care, and may optimize queries incorrectly if
+     whether the rows returned violate any <literal>NOT NULL</literal>
+     constraints that were defined on the foreign table columns &mdash; but
+     the planner does care, and may optimize queries incorrectly if
      <literal>NULL</> values are present in a column declared not to contain
      them.  If a <literal>NULL</> value is encountered when the user has
      declared that none should be present, it may be appropriate to raise an
@@ -277,6 +273,356 @@ EndForeignScan (ForeignScanState *node);
      to remote servers should be cleaned up.
     </para>
 
+   </sect2>
+
+   <sect2 id="fdw-callbacks-update">
+    <title>FDW Routines For Updating Foreign Tables</title>
+
+    <para>
+     If an FDW supports writable foreign tables, it should provide
+     some or all of the following callback functions depending on
+     the needs and capabilities of the FDW:
+    </para>
+
+    <para>
+<programlisting>
+void
+AddForeignUpdateTargets (Query *parsetree,
+                         RangeTblEntry *target_rte,
+                         Relation target_relation);
+</programlisting>
+
+     <command>UPDATE</> and <command>DELETE</> operations are performed
+     against rows previously fetched by the table-scanning functions.  The
+     FDW may need extra information, such as a row ID or the values of
+     primary-key columns, to ensure that it can identify the exact row to
+     update or delete.  To support that, this function can add extra hidden,
+     or <quote>junk</>, target columns to the list of columns that are to be
+     retrieved from the foreign table during an <command>UPDATE</> or
+     <command>DELETE</>.
+    </para>
+
+    <para>
+     To do that, add <structname>TargetEntry</> items to
+     <literal>parsetree-&gt;targetList</>, containing expressions for the
+     extra values to be fetched.  Each such entry must be marked
+     <structfield>resjunk</> = <literal>true</>, and must have a distinct
+     <structfield>resname</> that will identify it at execution time.
+     Avoid using names matching <literal>ctid<replaceable>N</></literal> or
+     <literal>wholerow<replaceable>N</></literal>, as the core system can
+     generate junk columns of these names.
+    </para>
+
+    <para>
+     This function is called in the rewriter, not the planner, so the
+     information available is a bit different from that available to the
+     planning routines.
+     <literal>parsetree</> is the parse tree for the <command>UPDATE</> or
+     <command>DELETE</> command, while <literal>target_rte</> and
+     <literal>target_relation</> describe the target foreign table.
+    </para>
+
+    <para>
+     If the <function>AddForeignUpdateTargets</> pointer is set to
+     <literal>NULL</>, no extra target expressions are added.
+     (This will make it impossible to implement <command>DELETE</>
+     operations, though <command>UPDATE</> may still be feasible if the FDW
+     relies on an unchanging primary key to identify rows.)
+    </para>
+
+    <para>
+<programlisting>
+List *
+PlanForeignModify (PlannerInfo *root,
+                   ModifyTable *plan,
+                   Index resultRelation,
+                   int subplan_index);
+</programlisting>
+
+     Perform any additional planning actions needed for an insert, update, or
+     delete on a foreign table.  This function generates the FDW-private
+     information that will be attached to the <structname>ModifyTable</> plan
+     node that performs the update action.  This private information must
+     have the form of a <literal>List</>, and will be delivered to
+     <function>BeginForeignModify</> during the execution stage.
+    </para>
+
+    <para>
+     <literal>root</> is the planner's global information about the query.
+     <literal>plan</> is the <structname>ModifyTable</> plan node, which is
+     complete except for the <structfield>fdwPrivLists</> field.
+     <literal>resultRelation</> identifies the target foreign table by its
+     rangetable index.  <literal>subplan_index</> identifies which target of
+     the <structname>ModifyTable</> plan node this is, counting from zero;
+     use this if you want to index into <literal>node-&gt;plans</> or other
+     substructure of the <literal>plan</> node.
+    </para>
+
+    <para>
+     See <xref linkend="fdw-planning"> for additional information.
+    </para>
+
+    <para>
+     If the <function>PlanForeignModify</> pointer is set to
+     <literal>NULL</>, no additional plan-time actions are taken, and the
+     <literal>fdw_private</> list delivered to
+     <function>BeginForeignModify</> will be NIL.
+    </para>
+
+    <para>
+<programlisting>
+void
+BeginForeignModify (ModifyTableState *mtstate,
+                    ResultRelInfo *rinfo,
+                    List *fdw_private,
+                    int subplan_index,
+                    int eflags);
+</programlisting>
+
+     Begin executing a foreign table modification operation.  This routine is
+     called during executor startup.  It should perform any initialization
+     needed prior to the actual table modifications.  Subsequently,
+     <function>ExecForeignInsert</>, <function>ExecForeignUpdate</> or
+     <function>ExecForeignDelete</> will be called for each tuple to be
+     inserted, updated, or deleted.
+    </para>
+
+    <para>
+     <literal>mtstate</> is the overall state of the
+     <structname>ModifyTable</> plan node being executed; global data about
+     the plan and execution state is available via this structure.
+     <literal>rinfo</> is the <structname>ResultRelInfo</> struct describing
+     the target foreign table.  (The <structfield>ri_FdwState</> field of
+     <structname>ResultRelInfo</> is available for the FDW to store any
+     private state it needs for this operation.)
+     <literal>fdw_private</> contains the private data generated by
+     <function>PlanForeignModify</>, if any.
+     <literal>subplan_index</> identifies which target of
+     the <structname>ModifyTable</> plan node this is.
+     <literal>eflags</> contains flag bits describing the executor's
+     operating mode for this plan node.
+    </para>
+
+    <para>
+     Note that when <literal>(eflags &amp; EXEC_FLAG_EXPLAIN_ONLY)</> is
+     true, this function should not perform any externally-visible actions;
+     it should only do the minimum required to make the node state valid
+     for <function>ExplainForeignModify</> and <function>EndForeignModify</>.
+    </para>
+
+    <para>
+     If the <function>BeginForeignModify</> pointer is set to
+     <literal>NULL</>, no action is taken during executor startup.
+    </para>
+
+    <para>
+<programlisting>
+TupleTableSlot *
+ExecForeignInsert (EState *estate,
+                   ResultRelInfo *rinfo,
+                   TupleTableSlot *slot,
+                   TupleTableSlot *planSlot);
+</programlisting>
+
+     Insert one tuple into the foreign table.
+     <literal>estate</> is global execution state for the query.
+     <literal>rinfo</> is the <structname>ResultRelInfo</> struct describing
+     the target foreign table.
+     <literal>slot</> contains the tuple to be inserted; it will match the
+     rowtype definition of the foreign table.
+     <literal>planSlot</> contains the tuple that was generated by the
+     <structname>ModifyTable</> plan node's subplan; it differs from
+     <literal>slot</> in possibly containing additional <quote>junk</>
+     columns.  (The <literal>planSlot</> is typically of little interest
+     for <command>INSERT</> cases, but is provided for completeness.)
+    </para>
+
+    <para>
+     The return value is either a slot containing the data that was actually
+     inserted (this might differ from the data supplied, for example as a
+     result of trigger actions), or NULL if no row was actually inserted
+     (again, typically as a result of triggers).  The passed-in
+     <literal>slot</> can be re-used for this purpose.
+    </para>
+
+    <para>
+     The data in the returned slot is used only if the <command>INSERT</>
+     query has a <literal>RETURNING</> clause.  Hence, the FDW could choose
+     to optimize away returning some or all columns depending on the contents
+     of the <literal>RETURNING</> clause.  However, some slot must be
+     returned to indicate success, or the query's reported rowcount will be
+     wrong.
+    </para>
+
+    <para>
+     If the <function>ExecForeignInsert</> pointer is set to
+     <literal>NULL</>, attempts to insert into the foreign table will fail
+     with an error message.
+    </para>
+
+    <para>
+<programlisting>
+TupleTableSlot *
+ExecForeignUpdate (EState *estate,
+                   ResultRelInfo *rinfo,
+                   TupleTableSlot *slot,
+                   TupleTableSlot *planSlot);
+</programlisting>
+
+     Update one tuple in the foreign table.
+     <literal>estate</> is global execution state for the query.
+     <literal>rinfo</> is the <structname>ResultRelInfo</> struct describing
+     the target foreign table.
+     <literal>slot</> contains the new data for the tuple; it will match the
+     rowtype definition of the foreign table.
+     <literal>planSlot</> contains the tuple that was generated by the
+     <structname>ModifyTable</> plan node's subplan; it differs from
+     <literal>slot</> in possibly containing additional <quote>junk</>
+     columns.  In particular, any junk columns that were requested by
+     <function>AddForeignUpdateTargets</> will be available from this slot.
+    </para>
+
+    <para>
+     The return value is either a slot containing the row as it was actually
+     updated (this might differ from the data supplied, for example as a
+     result of trigger actions), or NULL if no row was actually updated
+     (again, typically as a result of triggers).  The passed-in
+     <literal>slot</> can be re-used for this purpose.
+    </para>
+
+    <para>
+     The data in the returned slot is used only if the <command>UPDATE</>
+     query has a <literal>RETURNING</> clause.  Hence, the FDW could choose
+     to optimize away returning some or all columns depending on the contents
+     of the <literal>RETURNING</> clause.  However, some slot must be
+     returned to indicate success, or the query's reported rowcount will be
+     wrong.
+    </para>
+
+    <para>
+     If the <function>ExecForeignUpdate</> pointer is set to
+     <literal>NULL</>, attempts to update the foreign table will fail
+     with an error message.
+    </para>
+
+    <para>
+<programlisting>
+TupleTableSlot *
+ExecForeignDelete (EState *estate,
+                   ResultRelInfo *rinfo,
+                   TupleTableSlot *slot,
+                   TupleTableSlot *planSlot);
+</programlisting>
+
+     Delete one tuple from the foreign table.
+     <literal>estate</> is global execution state for the query.
+     <literal>rinfo</> is the <structname>ResultRelInfo</> struct describing
+     the target foreign table.
+     <literal>slot</> contains nothing useful upon call, but can be used to
+     hold the returned tuple.
+     <literal>planSlot</> contains the tuple that was generated by the
+     <structname>ModifyTable</> plan node's subplan; in particular, it will
+     carry any junk columns that were requested by
+     <function>AddForeignUpdateTargets</>.  The junk column(s) must be used
+     to identify the tuple to be deleted.
+    </para>
+
+    <para>
+     The return value is either a slot containing the row that was deleted,
+     or NULL if no row was deleted (typically as a result of triggers).  The
+     passed-in <literal>slot</> can be used to hold the tuple to be returned.
+    </para>
+
+    <para>
+     The data in the returned slot is used only if the <command>DELETE</>
+     query has a <literal>RETURNING</> clause.  Hence, the FDW could choose
+     to optimize away returning some or all columns depending on the contents
+     of the <literal>RETURNING</> clause.  However, some slot must be
+     returned to indicate success, or the query's reported rowcount will be
+     wrong.
+    </para>
+
+    <para>
+     If the <function>ExecForeignDelete</> pointer is set to
+     <literal>NULL</>, attempts to delete from the foreign table will fail
+     with an error message.
+    </para>
+
+    <para>
+<programlisting>
+void
+EndForeignModify (EState *estate,
+                  ResultRelInfo *rinfo);
+</programlisting>
+
+     End the table update and release resources.  It is normally not important
+     to release palloc'd memory, but for example open files and connections
+     to remote servers should be cleaned up.
+    </para>
+
+    <para>
+     If the <function>EndForeignModify</> pointer is set to
+     <literal>NULL</>, no action is taken during executor shutdown.
+    </para>
+
+   </sect2>
+
+   <sect2 id="fdw-callbacks-explain">
+    <title>FDW Routines for <command>EXPLAIN</></title>
+
+    <para>
+<programlisting>
+void
+ExplainForeignScan (ForeignScanState *node,
+                    ExplainState *es);
+</programlisting>
+
+     Print additional <command>EXPLAIN</> output for a foreign table scan.
+     This function can call <function>ExplainPropertyText</> and
+     related functions to add fields to the <command>EXPLAIN</> output.
+     The flag fields in <literal>es</> can be used to determine what to
+     print, and the state of the <structname>ForeignScanState</> node
+     can be inspected to provide run-time statistics in the <command>EXPLAIN
+     ANALYZE</> case.
+    </para>
+
+    <para>
+     If the <function>ExplainForeignScan</> pointer is set to
+     <literal>NULL</>, no additional information is printed during
+     <command>EXPLAIN</>.
+    </para>
+
+    <para>
+<programlisting>
+void
+ExplainForeignModify (ModifyTableState *mtstate,
+                      ResultRelInfo *rinfo,
+                      List *fdw_private,
+                      int subplan_index,
+                      struct ExplainState *es);
+</programlisting>
+
+     Print additional <command>EXPLAIN</> output for a foreign table update.
+     This function can call <function>ExplainPropertyText</> and
+     related functions to add fields to the <command>EXPLAIN</> output.
+     The flag fields in <literal>es</> can be used to determine what to
+     print, and the state of the <structname>ModifyTableState</> node
+     can be inspected to provide run-time statistics in the <command>EXPLAIN
+     ANALYZE</> case.  The first four arguments are the same as for
+     <function>BeginForeignModify</>.
+    </para>
+
+    <para>
+     If the <function>ExplainForeignModify</> pointer is set to
+     <literal>NULL</>, no additional information is printed during
+     <command>EXPLAIN</>.
+    </para>
+
+   </sect2>
+
+   <sect2 id="fdw-callbacks-analyze">
+    <title>FDW Routines for <command>ANALYZE</></title>
+
     <para>
 <programlisting>
 bool
@@ -291,6 +637,9 @@ AnalyzeForeignTable (Relation relation,
      to a function that will collect sample rows from the table in
      <parameter>func</>, plus the estimated size of the table in pages in
      <parameter>totalpages</>.  Otherwise, return <literal>false</>.
+    </para>
+
+    <para>
      If the FDW does not support collecting statistics for any tables, the
      <function>AnalyzeForeignTable</> pointer can be set to <literal>NULL</>.
     </para>
@@ -314,11 +663,7 @@ AcquireSampleRowsFunc (Relation relation, int elevel,
      if the FDW does not have any concept of dead rows.)
     </para>
 
-    <para>
-     The <structname>FdwRoutine</> struct type is declared in
-     <filename>src/include/foreign/fdwapi.h</>, which see for additional
-     details.
-    </para>
+   </sect2>
 
    </sect1>
 
@@ -432,9 +777,10 @@ GetForeignServerByName(const char *name, bool missing_ok);
 
     <para>
      The FDW callback functions <function>GetForeignRelSize</>,
-     <function>GetForeignPaths</>, and <function>GetForeignPlan</> must fit
-     into the workings of the <productname>PostgreSQL</> planner.  Here are
-     some notes about what they must do.
+     <function>GetForeignPaths</>, <function>GetForeignPlan</>, and
+     <function>PlanForeignModify</> must fit into the workings of the
+     <productname>PostgreSQL</> planner.  Here are some notes about what
+     they must do.
     </para>
 
     <para>
@@ -546,6 +892,33 @@ GetForeignServerByName(const char *name, bool missing_ok);
      same as for an ordinary restriction clause.
     </para>
 
+    <para>
+     When planning an <command>UPDATE</> or <command>DELETE</>,
+     <function>PlanForeignModify</> can look up the <structname>RelOptInfo</>
+     struct for the foreign table and make use of the
+     <literal>baserel-&gt;fdw_private</> data previously created by the
+     scan-planning functions.  However, in <command>INSERT</> the target
+     table is not scanned so there is no <structname>RelOptInfo</> for it.
+    </para>
+
+    <para>
+     For an <command>UPDATE</> or <command>DELETE</> against an external data
+     source that supports concurrent updates, it is recommended that the
+     <literal>ForeignScan</> operation lock the rows that it fetches, perhaps
+     via the equivalent of <command>SELECT FOR UPDATE</>.  The FDW may also
+     choose to lock rows at fetch time when the foreign table is referenced
+     in a <command>SELECT FOR UPDATE/SHARE</>; if it does not, the
+     <literal>FOR UPDATE</> or <literal>FOR SHARE</> option is essentially a
+     no-op so far as the foreign table is concerned.  This behavior may yield
+     semantics slightly different from operations on local tables, where row
+     locking is customarily delayed as long as possible: remote rows may get
+     locked even though they subsequently fail locally-applied restriction or
+     join conditions.  However, matching the local semantics exactly would
+     require an additional remote access for every row, and might be
+     impossible anyway depending on what locking semantics the external data
+     source provides.
+    </para>
+
   </sect1>
 
  </chapter>
diff --git a/doc/src/sgml/file-fdw.sgml b/doc/src/sgml/file-fdw.sgml
index 4acb8264a45e91c7bf31faa7afee2969e35c5ad8..8c527612aeff350c96f819e5c229832c2bb76d18 100644
--- a/doc/src/sgml/file-fdw.sgml
+++ b/doc/src/sgml/file-fdw.sgml
@@ -13,6 +13,7 @@
   files in the server's file system.  Data files must be in a format
   that can be read by <command>COPY FROM</command>;
   see <xref linkend="sql-copy"> for details.
+  Access to such data files is currently read-only.
  </para>
 
  <para>
@@ -160,7 +161,7 @@
 
  <example>
  <title id="csvlog-fdw">Create a Foreign Table for PostgreSQL CSV Logs</title>
-   
+
   <para>
    One of the obvious uses for the <literal>file_fdw</> is to make
    the PostgreSQL activity log available as a table for querying.  To
@@ -217,8 +218,8 @@ OPTIONS ( filename '/home/josh/9.1/data/pg_log/pglog.csv', format 'csv' );
   </para>
 
   <para>
-   That's it &mdash; now you can query your log directly. In production, of course,
-   you would need to define some way to adjust to log rotation.
+   That's it &mdash; now you can query your log directly. In production, of
+   course, you would need to define some way to deal with log rotation.
   </para>
  </example>
 
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index 61b77774aeecf79add1476f15f8cbff794befded..61cc2aafc2480ad9f955de6804003c3699c998e3 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -61,7 +61,10 @@
 
  <para>
   Now you need only <command>SELECT</> from a foreign table to access
-  the data stored in its underlying remote table.
+  the data stored in its underlying remote table.  You can also modify
+  the remote table using <command>INSERT</>, <command>UPDATE</>, or
+  <command>DELETE</>.  (Of course, the remote user you have specified
+  in your user mapping must have privileges to do these things.)
  </para>
 
  <para>
diff --git a/doc/src/sgml/ref/create_foreign_data_wrapper.sgml b/doc/src/sgml/ref/create_foreign_data_wrapper.sgml
index d9936e8165999cbb8b6d6d41293596fdb7ab3688..e2d897fb21434c3ff30262b2f5896c3c5985aace 100644
--- a/doc/src/sgml/ref/create_foreign_data_wrapper.sgml
+++ b/doc/src/sgml/ref/create_foreign_data_wrapper.sgml
@@ -117,9 +117,10 @@ CREATE FOREIGN DATA WRAPPER <replaceable class="parameter">name</replaceable>
   <title>Notes</title>
 
   <para>
-   At the moment, the foreign-data wrapper functionality is rudimentary.
-   There is no support for updating a foreign table, and optimization of
-   queries is primitive (and mostly left to the wrapper, too).
+   <productname>PostgreSQL</>'s foreign-data functionality is still under
+   active development.  Optimization of queries is primitive (and mostly left
+   to the wrapper, too).  Thus, there is considerable room for future
+   performance improvements.
   </para>
  </refsect1>
 
@@ -158,7 +159,7 @@ CREATE FOREIGN DATA WRAPPER mywrapper
    9075-9 (SQL/MED), with the exception that the <literal>HANDLER</literal>
    and <literal>VALIDATOR</literal> clauses are extensions and the standard
    clauses <literal>LIBRARY</literal> and <literal>LANGUAGE</literal>
-   are not implemented in PostgreSQL.
+   are not implemented in <productname>PostgreSQL</>.
   </para>
 
   <para>
@@ -175,6 +176,7 @@ CREATE FOREIGN DATA WRAPPER mywrapper
    <member><xref linkend="sql-dropforeigndatawrapper"></member>
    <member><xref linkend="sql-createserver"></member>
    <member><xref linkend="sql-createusermapping"></member>
+   <member><xref linkend="sql-createforeigntable"></member>
   </simplelist>
  </refsect1>
 
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 4825bca363f80d164d3a3ef724936546f04fb904..4eb94a43add81cd7917076f63aa84c6ec124525f 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2121,17 +2121,10 @@ CopyFrom(CopyState cstate)
 	 * here that basically duplicated execUtils.c ...)
 	 */
 	resultRelInfo = makeNode(ResultRelInfo);
-	resultRelInfo->ri_RangeTableIndex = 1;		/* dummy */
-	resultRelInfo->ri_RelationDesc = cstate->rel;
-	resultRelInfo->ri_TrigDesc = CopyTriggerDesc(cstate->rel->trigdesc);
-	if (resultRelInfo->ri_TrigDesc)
-	{
-		resultRelInfo->ri_TrigFunctions = (FmgrInfo *)
-			palloc0(resultRelInfo->ri_TrigDesc->numtriggers * sizeof(FmgrInfo));
-		resultRelInfo->ri_TrigWhenExprs = (List **)
-			palloc0(resultRelInfo->ri_TrigDesc->numtriggers * sizeof(List *));
-	}
-	resultRelInfo->ri_TrigInstrument = NULL;
+	InitResultRelInfo(resultRelInfo,
+					  cstate->rel,
+					  1,		/* dummy rangetable index */
+					  0);
 
 	ExecOpenIndices(resultRelInfo);
 
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 989b52da9d42d8fa22a39696503d6e7b434f3412..9799e9ecb417affe5a44994530f1458ed2acd778 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -90,6 +90,7 @@ static void ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir,
 static void ExplainScanTarget(Scan *plan, ExplainState *es);
 static void ExplainModifyTarget(ModifyTable *plan, ExplainState *es);
 static void ExplainTargetRel(Plan *plan, Index rti, ExplainState *es);
+static void show_modifytable_info(ModifyTableState *mtstate, ExplainState *es);
 static void ExplainMemberNodes(List *plans, PlanState **planstates,
 				   List *ancestors, ExplainState *es);
 static void ExplainSubPlans(List *plans, List *ancestors,
@@ -1341,6 +1342,9 @@ ExplainNode(PlanState *planstate, List *ancestors,
 				show_instrumentation_count("Rows Removed by Filter", 1,
 										   planstate, es);
 			break;
+		case T_ModifyTable:
+			show_modifytable_info((ModifyTableState *) planstate, es);
+			break;
 		case T_Hash:
 			show_hash_info((HashState *) planstate, es);
 			break;
@@ -1840,7 +1844,8 @@ show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es)
 	FdwRoutine *fdwroutine = fsstate->fdwroutine;
 
 	/* Let the FDW emit whatever fields it wants */
-	fdwroutine->ExplainForeignScan(fsstate, es);
+	if (fdwroutine->ExplainForeignScan != NULL)
+		fdwroutine->ExplainForeignScan(fsstate, es);
 }
 
 /*
@@ -2036,6 +2041,34 @@ ExplainTargetRel(Plan *plan, Index rti, ExplainState *es)
 	}
 }
 
+/*
+ * Show extra information for a ModifyTable node
+ */
+static void
+show_modifytable_info(ModifyTableState *mtstate, ExplainState *es)
+{
+	FdwRoutine *fdwroutine = mtstate->resultRelInfo->ri_FdwRoutine;
+
+	/*
+	 * If the first target relation is a foreign table, call its FDW to
+	 * display whatever additional fields it wants to.	For now, we ignore the
+	 * possibility of other targets being foreign tables, although the API for
+	 * ExplainForeignModify is designed to allow them to be processed.
+	 */
+	if (fdwroutine != NULL &&
+		fdwroutine->ExplainForeignModify != NULL)
+	{
+		ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
+		List	   *fdw_private = (List *) linitial(node->fdwPrivLists);
+
+		fdwroutine->ExplainForeignModify(mtstate,
+										 mtstate->resultRelInfo,
+										 fdw_private,
+										 0,
+										 es);
+	}
+}
+
 /*
  * Explain the constituent plans of a ModifyTable, Append, MergeAppend,
  * BitmapAnd, or BitmapOr node.
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 288b29e44a941bf40690477c67c41d75dd105709..1f2a23bcdd6d15c95bd2d0e812e6a6d0064aaa32 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -44,6 +44,7 @@
 #include "catalog/namespace.h"
 #include "commands/trigger.h"
 #include "executor/execdebug.h"
+#include "foreign/fdwapi.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "optimizer/clauses.h"
@@ -1005,6 +1006,7 @@ void
 CheckValidResultRel(Relation resultRel, CmdType operation)
 {
 	TriggerDesc *trigDesc = resultRel->trigdesc;
+	FdwRoutine *fdwroutine;
 
 	switch (resultRel->rd_rel->relkind)
 	{
@@ -1069,10 +1071,35 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
 							RelationGetRelationName(resultRel))));
 			break;
 		case RELKIND_FOREIGN_TABLE:
-			ereport(ERROR,
-					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("cannot change foreign table \"%s\"",
-							RelationGetRelationName(resultRel))));
+			/* Okay only if the FDW supports it */
+			fdwroutine = GetFdwRoutineForRelation(resultRel, false);
+			switch (operation)
+			{
+				case CMD_INSERT:
+					if (fdwroutine->ExecForeignInsert == NULL)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cannot insert into foreign table \"%s\"",
+										RelationGetRelationName(resultRel))));
+					break;
+				case CMD_UPDATE:
+					if (fdwroutine->ExecForeignUpdate == NULL)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cannot update foreign table \"%s\"",
+										RelationGetRelationName(resultRel))));
+					break;
+				case CMD_DELETE:
+					if (fdwroutine->ExecForeignDelete == NULL)
+						ereport(ERROR,
+								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+								 errmsg("cannot delete from foreign table \"%s\"",
+										RelationGetRelationName(resultRel))));
+					break;
+				default:
+					elog(ERROR, "unrecognized CmdType: %d", (int) operation);
+					break;
+			}
 			break;
 		default:
 			ereport(ERROR,
@@ -1126,7 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
 							RelationGetRelationName(rel))));
 			break;
 		case RELKIND_FOREIGN_TABLE:
-			/* Perhaps we can support this someday, but not today */
+			/* Should not get here */
 			ereport(ERROR,
 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 					 errmsg("cannot lock rows in foreign table \"%s\"",
@@ -1180,6 +1207,11 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 		resultRelInfo->ri_TrigWhenExprs = NULL;
 		resultRelInfo->ri_TrigInstrument = NULL;
 	}
+	if (resultRelationDesc->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+		resultRelInfo->ri_FdwRoutine = GetFdwRoutineForRelation(resultRelationDesc, true);
+	else
+		resultRelInfo->ri_FdwRoutine = NULL;
+	resultRelInfo->ri_FdwState = NULL;
 	resultRelInfo->ri_ConstraintExprs = NULL;
 	resultRelInfo->ri_junkFilter = NULL;
 	resultRelInfo->ri_projectReturning = NULL;
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 63478cd12ad311f692b94cab86c0093ec7c2a348..448fd6a912fae807075341dd2f8b20dae5386242 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -147,7 +147,8 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
 	scanstate->ss.ss_currentRelation = currentRelation;
 
 	/*
-	 * get the scan type from the relation descriptor.
+	 * get the scan type from the relation descriptor.	(XXX at some point we
+	 * might want to let the FDW editorialize on the scan tupdesc.)
 	 */
 	ExecAssignScanType(&scanstate->ss, RelationGetDescr(currentRelation));
 
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index cb084d03d47759cc10f200a71d3bb8e6f4d3a4ce..a6f247e1bc36474fdce98c55ebf8c43cc004affe 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
 #include "commands/trigger.h"
 #include "executor/executor.h"
 #include "executor/nodeModifyTable.h"
+#include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "storage/bufmgr.h"
@@ -225,6 +226,24 @@ ExecInsert(TupleTableSlot *slot,
 
 		newId = InvalidOid;
 	}
+	else if (resultRelInfo->ri_FdwRoutine)
+	{
+		/*
+		 * insert into foreign table: let the FDW do it
+		 */
+		slot = resultRelInfo->ri_FdwRoutine->ExecForeignInsert(estate,
+															   resultRelInfo,
+															   slot,
+															   planSlot);
+
+		if (slot == NULL)		/* "do nothing" */
+			return NULL;
+
+		/* FDW might have changed tuple */
+		tuple = ExecMaterializeSlot(slot);
+
+		newId = InvalidOid;
+	}
 	else
 	{
 		/*
@@ -279,7 +298,9 @@ ExecInsert(TupleTableSlot *slot,
  *		When deleting from a table, tupleid identifies the tuple to
  *		delete and oldtuple is NULL.  When deleting from a view,
  *		oldtuple is passed to the INSTEAD OF triggers and identifies
- *		what to delete, and tupleid is invalid.
+ *		what to delete, and tupleid is invalid.  When deleting from a
+ *		foreign table, both tupleid and oldtuple are NULL; the FDW has
+ *		to figure out which row to delete using data from the planSlot.
  *
  *		Returns RETURNING result if any, otherwise NULL.
  * ----------------------------------------------------------------
@@ -296,6 +317,7 @@ ExecDelete(ItemPointer tupleid,
 	Relation	resultRelationDesc;
 	HTSU_Result result;
 	HeapUpdateFailureData hufd;
+	TupleTableSlot *slot = NULL;
 
 	/*
 	 * get information on the (current) result relation
@@ -334,6 +356,27 @@ ExecDelete(ItemPointer tupleid,
 		if (!dodelete)			/* "do nothing" */
 			return NULL;
 	}
+	else if (resultRelInfo->ri_FdwRoutine)
+	{
+		/*
+		 * delete from foreign table: let the FDW do it
+		 *
+		 * We offer the trigger tuple slot as a place to store RETURNING data,
+		 * although the FDW can return some other slot if it wants.  Set up
+		 * the slot's tupdesc so the FDW doesn't need to do that for itself.
+		 */
+		slot = estate->es_trig_tuple_slot;
+		if (slot->tts_tupleDescriptor != RelationGetDescr(resultRelationDesc))
+			ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc));
+
+		slot = resultRelInfo->ri_FdwRoutine->ExecForeignDelete(estate,
+															   resultRelInfo,
+															   slot,
+															   planSlot);
+
+		if (slot == NULL)		/* "do nothing" */
+			return NULL;
+	}
 	else
 	{
 		/*
@@ -443,34 +486,49 @@ ldelete:;
 		 * We have to put the target tuple into a slot, which means first we
 		 * gotta fetch it.	We can use the trigger tuple slot.
 		 */
-		TupleTableSlot *slot = estate->es_trig_tuple_slot;
 		TupleTableSlot *rslot;
 		HeapTupleData deltuple;
 		Buffer		delbuffer;
 
-		if (oldtuple != NULL)
+		if (resultRelInfo->ri_FdwRoutine)
 		{
-			deltuple.t_data = oldtuple;
-			deltuple.t_len = HeapTupleHeaderGetDatumLength(oldtuple);
-			ItemPointerSetInvalid(&(deltuple.t_self));
-			deltuple.t_tableOid = InvalidOid;
+			/* FDW must have provided a slot containing the deleted row */
+			Assert(!TupIsNull(slot));
 			delbuffer = InvalidBuffer;
 		}
 		else
 		{
-			deltuple.t_self = *tupleid;
-			if (!heap_fetch(resultRelationDesc, SnapshotAny,
-							&deltuple, &delbuffer, false, NULL))
-				elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
-		}
+			slot = estate->es_trig_tuple_slot;
+			if (oldtuple != NULL)
+			{
+				deltuple.t_data = oldtuple;
+				deltuple.t_len = HeapTupleHeaderGetDatumLength(oldtuple);
+				ItemPointerSetInvalid(&(deltuple.t_self));
+				deltuple.t_tableOid = InvalidOid;
+				delbuffer = InvalidBuffer;
+			}
+			else
+			{
+				deltuple.t_self = *tupleid;
+				if (!heap_fetch(resultRelationDesc, SnapshotAny,
+								&deltuple, &delbuffer, false, NULL))
+					elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
+			}
 
-		if (slot->tts_tupleDescriptor != RelationGetDescr(resultRelationDesc))
-			ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc));
-		ExecStoreTuple(&deltuple, slot, InvalidBuffer, false);
+			if (slot->tts_tupleDescriptor != RelationGetDescr(resultRelationDesc))
+				ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc));
+			ExecStoreTuple(&deltuple, slot, InvalidBuffer, false);
+		}
 
 		rslot = ExecProcessReturning(resultRelInfo->ri_projectReturning,
 									 slot, planSlot);
 
+		/*
+		 * Before releasing the target tuple again, make sure rslot has a
+		 * local copy of any pass-by-reference values.
+		 */
+		ExecMaterializeSlot(rslot);
+
 		ExecClearTuple(slot);
 		if (BufferIsValid(delbuffer))
 			ReleaseBuffer(delbuffer);
@@ -494,7 +552,9 @@ ldelete:;
  *		When updating a table, tupleid identifies the tuple to
  *		update and oldtuple is NULL.  When updating a view, oldtuple
  *		is passed to the INSTEAD OF triggers and identifies what to
- *		update, and tupleid is invalid.
+ *		update, and tupleid is invalid.  When updating a foreign table,
+ *		both tupleid and oldtuple are NULL; the FDW has to figure out
+ *		which row to update using data from the planSlot.
  *
  *		Returns RETURNING result if any, otherwise NULL.
  * ----------------------------------------------------------------
@@ -568,6 +628,22 @@ ExecUpdate(ItemPointer tupleid,
 		/* trigger might have changed tuple */
 		tuple = ExecMaterializeSlot(slot);
 	}
+	else if (resultRelInfo->ri_FdwRoutine)
+	{
+		/*
+		 * update in foreign table: let the FDW do it
+		 */
+		slot = resultRelInfo->ri_FdwRoutine->ExecForeignUpdate(estate,
+															   resultRelInfo,
+															   slot,
+															   planSlot);
+
+		if (slot == NULL)		/* "do nothing" */
+			return NULL;
+
+		/* FDW might have changed tuple */
+		tuple = ExecMaterializeSlot(slot);
+	}
 	else
 	{
 		LockTupleMode	lockmode;
@@ -867,10 +943,12 @@ ExecModifyTable(ModifyTableState *node)
 			 */
 			if (operation == CMD_UPDATE || operation == CMD_DELETE)
 			{
+				char		relkind;
 				Datum		datum;
 				bool		isNull;
 
-				if (resultRelInfo->ri_RelationDesc->rd_rel->relkind == RELKIND_RELATION)
+				relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
+				if (relkind == RELKIND_RELATION)
 				{
 					datum = ExecGetJunkAttribute(slot,
 												 junkfilter->jf_junkAttNo,
@@ -884,6 +962,10 @@ ExecModifyTable(ModifyTableState *node)
 												 * ctid!! */
 					tupleid = &tuple_ctid;
 				}
+				else if (relkind == RELKIND_FOREIGN_TABLE)
+				{
+					/* do nothing; FDW must fetch any junk attrs it wants */
+				}
 				else
 				{
 					datum = ExecGetJunkAttribute(slot,
@@ -1026,6 +1108,19 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		estate->es_result_relation_info = resultRelInfo;
 		mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
 
+		/* Also let FDWs init themselves for foreign-table result rels */
+		if (resultRelInfo->ri_FdwRoutine != NULL &&
+			resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
+		{
+			List	   *fdw_private = (List *) list_nth(node->fdwPrivLists, i);
+
+			resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
+															 resultRelInfo,
+															 fdw_private,
+															 i,
+															 eflags);
+		}
+
 		resultRelInfo++;
 		i++;
 	}
@@ -1180,12 +1275,19 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 				if (operation == CMD_UPDATE || operation == CMD_DELETE)
 				{
 					/* For UPDATE/DELETE, find the appropriate junk attr now */
-					if (resultRelInfo->ri_RelationDesc->rd_rel->relkind == RELKIND_RELATION)
+					char		relkind;
+
+					relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
+					if (relkind == RELKIND_RELATION)
 					{
 						j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
 						if (!AttributeNumberIsValid(j->jf_junkAttNo))
 							elog(ERROR, "could not find junk ctid column");
 					}
+					else if (relkind == RELKIND_FOREIGN_TABLE)
+					{
+						/* FDW must fetch any junk attrs it wants */
+					}
 					else
 					{
 						j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
@@ -1243,6 +1345,19 @@ ExecEndModifyTable(ModifyTableState *node)
 {
 	int			i;
 
+	/*
+	 * Allow any FDWs to shut down
+	 */
+	for (i = 0; i < node->mt_nplans; i++)
+	{
+		ResultRelInfo *resultRelInfo = node->resultRelInfo + i;
+
+		if (resultRelInfo->ri_FdwRoutine != NULL &&
+			resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+			resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
+														   resultRelInfo);
+	}
+
 	/*
 	 * Free the exprcontext
 	 */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 867b0c09d9010307a4f5f564423ef86e825e430e..fd3823a36ee474626fc7e42c903bbfde491f546c 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -179,6 +179,7 @@ _copyModifyTable(const ModifyTable *from)
 	COPY_SCALAR_FIELD(resultRelIndex);
 	COPY_NODE_FIELD(plans);
 	COPY_NODE_FIELD(returningLists);
+	COPY_NODE_FIELD(fdwPrivLists);
 	COPY_NODE_FIELD(rowMarks);
 	COPY_SCALAR_FIELD(epqParam);
 
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index be4e5482816f0199b6271d1d346aff2fc8926f95..d8ce5753a4c95b348262d2e9bac7558e7553155b 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -333,6 +333,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
 	WRITE_INT_FIELD(resultRelIndex);
 	WRITE_NODE_FIELD(plans);
 	WRITE_NODE_FIELD(returningLists);
+	WRITE_NODE_FIELD(fdwPrivLists);
 	WRITE_NODE_FIELD(rowMarks);
 	WRITE_INT_FIELD(epqParam);
 }
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 8a51b56dde2303562e9e370929967bc134a96661..d668128ccb12d8a42d0dca0b5e3f4667439ce3ce 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -20,6 +20,7 @@
 #include <math.h>
 
 #include "access/skey.h"
+#include "catalog/pg_class.h"
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -4695,7 +4696,8 @@ make_result(PlannerInfo *root,
  * to make it look better sometime.
  */
 ModifyTable *
-make_modifytable(CmdType operation, bool canSetTag,
+make_modifytable(PlannerInfo *root,
+				 CmdType operation, bool canSetTag,
 				 List *resultRelations,
 				 List *subplans, List *returningLists,
 				 List *rowMarks, int epqParam)
@@ -4703,7 +4705,10 @@ make_modifytable(CmdType operation, bool canSetTag,
 	ModifyTable *node = makeNode(ModifyTable);
 	Plan	   *plan = &node->plan;
 	double		total_size;
+	List	   *fdw_private_list;
 	ListCell   *subnode;
+	ListCell   *lc;
+	int			i;
 
 	Assert(list_length(resultRelations) == list_length(subplans));
 	Assert(returningLists == NIL ||
@@ -4746,6 +4751,53 @@ make_modifytable(CmdType operation, bool canSetTag,
 	node->rowMarks = rowMarks;
 	node->epqParam = epqParam;
 
+	/*
+	 * For each result relation that is a foreign table, allow the FDW to
+	 * construct private plan data, and accumulate it all into a list.
+	 */
+	fdw_private_list = NIL;
+	i = 0;
+	foreach(lc, resultRelations)
+	{
+		Index		rti = lfirst_int(lc);
+		FdwRoutine *fdwroutine;
+		List	   *fdw_private;
+
+		/*
+		 * If possible, we want to get the FdwRoutine from our RelOptInfo for
+		 * the table.  But sometimes we don't have a RelOptInfo and must get
+		 * it the hard way.  (In INSERT, the target relation is not scanned,
+		 * so it's not a baserel; and there are also corner cases for
+		 * updatable views where the target rel isn't a baserel.)
+		 */
+		if (rti < root->simple_rel_array_size &&
+			root->simple_rel_array[rti] != NULL)
+		{
+			RelOptInfo *resultRel = root->simple_rel_array[rti];
+
+			fdwroutine = resultRel->fdwroutine;
+		}
+		else
+		{
+			RangeTblEntry *rte = planner_rt_fetch(rti, root);
+
+			Assert(rte->rtekind == RTE_RELATION);
+			if (rte->relkind == RELKIND_FOREIGN_TABLE)
+				fdwroutine = GetFdwRoutineByRelId(rte->relid);
+			else
+				fdwroutine = NULL;
+		}
+
+		if (fdwroutine != NULL &&
+			fdwroutine->PlanForeignModify != NULL)
+			fdw_private = fdwroutine->PlanForeignModify(root, node, rti, i);
+		else
+			fdw_private = NIL;
+		fdw_private_list = lappend(fdw_private_list, fdw_private);
+		i++;
+	}
+	node->fdwPrivLists = fdw_private_list;
+
 	return node;
 }
 
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index db3d5c501824a4ae8cd1a967c9de705846e3b20e..0847e787c392f90705347859ac4e5158c11d2f87 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -571,7 +571,8 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 			else
 				rowMarks = root->rowMarks;
 
-			plan = (Plan *) make_modifytable(parse->commandType,
+			plan = (Plan *) make_modifytable(root,
+											 parse->commandType,
 											 parse->canSetTag,
 									   list_make1_int(parse->resultRelation),
 											 list_make1(plan),
@@ -964,7 +965,8 @@ inheritance_planner(PlannerInfo *root)
 		rowMarks = root->rowMarks;
 
 	/* And last, tack on a ModifyTable node to do the UPDATE/DELETE work */
-	return (Plan *) make_modifytable(parse->commandType,
+	return (Plan *) make_modifytable(root,
+									 parse->commandType,
 									 parse->canSetTag,
 									 resultRelations,
 									 subplans,
@@ -2035,6 +2037,15 @@ preprocess_rowmarks(PlannerInfo *root)
 		if (rte->rtekind != RTE_RELATION)
 			continue;
 
+		/*
+		 * Similarly, ignore RowMarkClauses for foreign tables; foreign tables
+		 * will instead get ROW_MARK_COPY items in the next loop.  (FDWs might
+		 * choose to do something special while fetching their rows, but that
+		 * is of no concern here.)
+		 */
+		if (rte->relkind == RELKIND_FOREIGN_TABLE)
+			continue;
+
 		rels = bms_del_member(rels, rc->rti);
 
 		newrc = makeNode(PlanRowMark);
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index 7b28b5584b9eb8268e75b1222e71bf32defdbedf..fb67f9e4447dcb60d49fde1cdc077738df5dce10 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -6,7 +6,8 @@
  * For INSERT and UPDATE queries, the targetlist must contain an entry for
  * each attribute of the target relation in the correct order.	For all query
  * types, we may need to add junk tlist entries for Vars used in the RETURNING
- * list and row ID information needed for EvalPlanQual checking.
+ * list and row ID information needed for SELECT FOR UPDATE locking and/or
+ * EvalPlanQual checking.
  *
  * NOTE: the rewriter's rewriteTargetListIU and rewriteTargetListUD
  * routines also do preprocessing of the targetlist.  The division of labor
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index d34fca54666a4a83d2d77d04f5b1b3a93bcfd34a..2a943f9c6a57246154a9eb5df9a71312f1a9ce85 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2164,7 +2164,7 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 
 
 /*
- * Check for features that are not supported together with FOR [KEY] UPDATE/SHARE.
+ * Check for features that are not supported with FOR [KEY] UPDATE/SHARE.
  *
  * exported so planner can check again after rewriting, query pullup, etc
  */
@@ -2239,9 +2239,6 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 			switch (rte->rtekind)
 			{
 				case RTE_RELATION:
-					/* ignore foreign tables */
-					if (rte->relkind == RELKIND_FOREIGN_TABLE)
-						break;
 					applyLockingClause(qry, i,
 									   lc->strength, lc->noWait, pushedDown);
 					rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
@@ -2251,7 +2248,7 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 									   lc->strength, lc->noWait, pushedDown);
 
 					/*
-					 * FOR [KEY] UPDATE/SHARE of subquery is propagated to all of
+					 * FOR UPDATE/SHARE of subquery is propagated to all of
 					 * subquery's rels, too.  We could do this later (based on
 					 * the marking of the subquery RTE) but it is convenient
 					 * to have local knowledge in each query level about which
@@ -2291,12 +2288,6 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
 					switch (rte->rtekind)
 					{
 						case RTE_RELATION:
-							if (rte->relkind == RELKIND_FOREIGN_TABLE)
-								ereport(ERROR,
-									 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-									  errmsg("row-level locks cannot be used with foreign table \"%s\"",
-											 rte->eref->aliasname),
-									  parser_errposition(pstate, thisrel->location)));
 							applyLockingClause(qry, i,
 											   lc->strength, lc->noWait,
 											   pushedDown);
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 83c83a6a8a44cfa9fa0b42f3be0d3684fcdf8ef1..b99b9930d6a836903a3513b95fd487a322d8c589 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -16,6 +16,7 @@
 #include "access/sysattr.h"
 #include "catalog/pg_type.h"
 #include "commands/trigger.h"
+#include "foreign/fdwapi.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/analyze.h"
@@ -1156,6 +1157,7 @@ rewriteValuesRTE(RangeTblEntry *rte, Relation target_relation, List *attrnos)
  * is a regular table, the junk TLE emits the ctid attribute of the original
  * row.  When the target relation is a view, there is no ctid, so we instead
  * emit a whole-row Var that will contain the "old" values of the view row.
+ * If it's a foreign table, we let the FDW decide what to add.
  *
  * For UPDATE queries, this is applied after rewriteTargetListIU.  The
  * ordering isn't actually critical at the moment.
@@ -1183,6 +1185,21 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 
 		attrname = "ctid";
 	}
+	else if (target_relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+	{
+		/*
+		 * Let the foreign table's FDW add whatever junk TLEs it wants.
+		 */
+		FdwRoutine *fdwroutine;
+
+		fdwroutine = GetFdwRoutineForRelation(target_relation, false);
+
+		if (fdwroutine->AddForeignUpdateTargets != NULL)
+			fdwroutine->AddForeignUpdateTargets(parsetree, target_rte,
+												target_relation);
+
+		return;
+	}
 	else
 	{
 		/*
@@ -1444,17 +1461,13 @@ markQueryForLocking(Query *qry, Node *jtnode,
 
 		if (rte->rtekind == RTE_RELATION)
 		{
-			/* ignore foreign tables */
-			if (rte->relkind != RELKIND_FOREIGN_TABLE)
-			{
-				applyLockingClause(qry, rti, strength, noWait, pushedDown);
-				rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
-			}
+			applyLockingClause(qry, rti, strength, noWait, pushedDown);
+			rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
 		}
 		else if (rte->rtekind == RTE_SUBQUERY)
 		{
 			applyLockingClause(qry, rti, strength, noWait, pushedDown);
-			/* FOR [KEY] UPDATE/SHARE of subquery is propagated to subquery's rels */
+			/* FOR UPDATE/SHARE of subquery is propagated to subquery's rels */
 			markQueryForLocking(rte->subquery, (Node *) rte->subquery->jointree,
 								strength, noWait, true);
 		}
diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
index 562d5412df7021b01c8c71769b40007c9ebd349c..485eee320f8f4c2c9a80188668eabf9f1a31e947 100644
--- a/src/include/foreign/fdwapi.h
+++ b/src/include/foreign/fdwapi.h
@@ -38,9 +38,6 @@ typedef ForeignScan *(*GetForeignPlan_function) (PlannerInfo *root,
 															 List *tlist,
 														 List *scan_clauses);
 
-typedef void (*ExplainForeignScan_function) (ForeignScanState *node,
-													struct ExplainState *es);
-
 typedef void (*BeginForeignScan_function) (ForeignScanState *node,
 													   int eflags);
 
@@ -50,6 +47,48 @@ typedef void (*ReScanForeignScan_function) (ForeignScanState *node);
 
 typedef void (*EndForeignScan_function) (ForeignScanState *node);
 
+typedef void (*AddForeignUpdateTargets_function) (Query *parsetree,
+												   RangeTblEntry *target_rte,
+												   Relation target_relation);
+
+typedef List *(*PlanForeignModify_function) (PlannerInfo *root,
+														 ModifyTable *plan,
+														 Index resultRelation,
+														 int subplan_index);
+
+typedef void (*BeginForeignModify_function) (ModifyTableState *mtstate,
+														 ResultRelInfo *rinfo,
+														 List *fdw_private,
+														 int subplan_index,
+														 int eflags);
+
+typedef TupleTableSlot *(*ExecForeignInsert_function) (EState *estate,
+														ResultRelInfo *rinfo,
+														TupleTableSlot *slot,
+												   TupleTableSlot *planSlot);
+
+typedef TupleTableSlot *(*ExecForeignUpdate_function) (EState *estate,
+														ResultRelInfo *rinfo,
+														TupleTableSlot *slot,
+												   TupleTableSlot *planSlot);
+
+typedef TupleTableSlot *(*ExecForeignDelete_function) (EState *estate,
+														ResultRelInfo *rinfo,
+														TupleTableSlot *slot,
+												   TupleTableSlot *planSlot);
+
+typedef void (*EndForeignModify_function) (EState *estate,
+													   ResultRelInfo *rinfo);
+
+typedef void (*ExplainForeignScan_function) (ForeignScanState *node,
+													struct ExplainState *es);
+
+typedef void (*ExplainForeignModify_function) (ModifyTableState *mtstate,
+														ResultRelInfo *rinfo,
+														   List *fdw_private,
+														   int subplan_index,
+													struct ExplainState *es);
+
 typedef int (*AcquireSampleRowsFunc) (Relation relation, int elevel,
 											   HeapTuple *rows, int targrows,
 												  double *totalrows,
@@ -73,22 +112,34 @@ typedef struct FdwRoutine
 {
 	NodeTag		type;
 
-	/*
-	 * These functions are required.
-	 */
+	/* Functions for scanning foreign tables */
 	GetForeignRelSize_function GetForeignRelSize;
 	GetForeignPaths_function GetForeignPaths;
 	GetForeignPlan_function GetForeignPlan;
-	ExplainForeignScan_function ExplainForeignScan;
 	BeginForeignScan_function BeginForeignScan;
 	IterateForeignScan_function IterateForeignScan;
 	ReScanForeignScan_function ReScanForeignScan;
 	EndForeignScan_function EndForeignScan;
 
 	/*
-	 * These functions are optional.  Set the pointer to NULL for any that are
-	 * not provided.
+	 * Remaining functions are optional.  Set the pointer to NULL for any that
+	 * are not provided.
 	 */
+
+	/* Functions for updating foreign tables */
+	AddForeignUpdateTargets_function AddForeignUpdateTargets;
+	PlanForeignModify_function PlanForeignModify;
+	BeginForeignModify_function BeginForeignModify;
+	ExecForeignInsert_function ExecForeignInsert;
+	ExecForeignUpdate_function ExecForeignUpdate;
+	ExecForeignDelete_function ExecForeignDelete;
+	EndForeignModify_function EndForeignModify;
+
+	/* Support functions for EXPLAIN */
+	ExplainForeignScan_function ExplainForeignScan;
+	ExplainForeignModify_function ExplainForeignModify;
+
+	/* Support functions for ANALYZE */
 	AnalyzeForeignTable_function AnalyzeForeignTable;
 } FdwRoutine;
 
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 76e8cdb1ad8d6ccb540369aeecae78c956c3b85b..4f77016652da5864561a1d0881140c58fbba6435 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -270,7 +270,8 @@ typedef struct ProjectionInfo
  *	  resultSlot:		tuple slot used to hold cleaned tuple.
  *	  junkAttNo:		not used by junkfilter code.  Can be used by caller
  *						to remember the attno of a specific junk attribute
- *						(execMain.c stores the "ctid" attno here).
+ *						(nodeModifyTable.c keeps the "ctid" or "wholerow"
+ *						attno here).
  * ----------------
  */
 typedef struct JunkFilter
@@ -300,6 +301,8 @@ typedef struct JunkFilter
  *		TrigFunctions			cached lookup info for trigger functions
  *		TrigWhenExprs			array of trigger WHEN expr states
  *		TrigInstrument			optional runtime measurements for triggers
+ *		FdwRoutine				FDW callback functions, if foreign table
+ *		FdwState				available to save private state of FDW
  *		ConstraintExprs			array of constraint-checking expr states
  *		junkFilter				for removing junk attributes from tuples
  *		projectReturning		for computing a RETURNING list
@@ -317,6 +320,8 @@ typedef struct ResultRelInfo
 	FmgrInfo   *ri_TrigFunctions;
 	List	  **ri_TrigWhenExprs;
 	Instrumentation *ri_TrigInstrument;
+	struct FdwRoutine *ri_FdwRoutine;
+	void	   *ri_FdwState;
 	List	  **ri_ConstraintExprs;
 	JunkFilter *ri_junkFilter;
 	ProjectionInfo *ri_projectReturning;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 0b8b1076bbffbc49ca214bc4d14f59ac90fd3701..841701ed98add7ff6b9f8b8ac30dc929b604fafe 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -173,6 +173,7 @@ typedef struct ModifyTable
 	int			resultRelIndex; /* index of first resultRel in plan's list */
 	List	   *plans;			/* plan(s) producing source data */
 	List	   *returningLists; /* per-target-table RETURNING tlists */
+	List	   *fdwPrivLists;	/* per-target-table FDW private data lists */
 	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
 	int			epqParam;		/* ID of Param for EvalPlanQual re-eval */
 } ModifyTable;
@@ -752,13 +753,32 @@ typedef struct Limit
  * RowMarkType -
  *	  enums for types of row-marking operations
  *
- * When doing UPDATE, DELETE, or SELECT FOR [KEY] UPDATE/SHARE, we have to uniquely
+ * The first four of these values represent different lock strengths that
+ * we can take on tuples according to SELECT FOR [KEY] UPDATE/SHARE requests.
+ * We only support these on regular tables.  For foreign tables, any locking
+ * that might be done for these requests must happen during the initial row
+ * fetch; there is no mechanism for going back to lock a row later (and thus
+ * no need for EvalPlanQual machinery during updates of foreign tables).
+ * This means that the semantics will be a bit different than for a local
+ * table; in particular we are likely to lock more rows than would be locked
+ * locally, since remote rows will be locked even if they then fail
+ * locally-checked restriction or join quals.  However, the alternative of
+ * doing a separate remote query to lock each selected row is extremely
+ * unappealing, so let's do it like this for now.
+ *
+ * When doing UPDATE, DELETE, or SELECT FOR UPDATE/SHARE, we have to uniquely
  * identify all the source rows, not only those from the target relations, so
  * that we can perform EvalPlanQual rechecking at need.  For plain tables we
- * can just fetch the TID, the same as for a target relation.  Otherwise (for
- * example for VALUES or FUNCTION scans) we have to copy the whole row value.
- * The latter is pretty inefficient but fortunately the case is not
- * performance-critical in practice.
+ * can just fetch the TID, much as for a target relation; this case is
+ * represented by ROW_MARK_REFERENCE.  Otherwise (for example for VALUES or
+ * FUNCTION scans) we have to copy the whole row value.  ROW_MARK_COPY is
+ * pretty inefficient, since most of the time we'll never need the data; but
+ * fortunately the case is not performance-critical in practice.  Note that
+ * we use ROW_MARK_COPY for non-target foreign tables, even if the FDW has a
+ * concept of rowid and so could theoretically support some form of
+ * ROW_MARK_REFERENCE.	Although copying the whole row value is inefficient,
+ * it's probably still faster than doing a second remote fetch, so it doesn't
+ * seem worth the extra complexity to permit ROW_MARK_REFERENCE.
  */
 typedef enum RowMarkType
 {
@@ -776,10 +796,10 @@ typedef enum RowMarkType
  * PlanRowMark -
  *	   plan-time representation of FOR [KEY] UPDATE/SHARE clauses
  *
- * When doing UPDATE, DELETE, or SELECT FOR [KEY] UPDATE/SHARE, we create a separate
+ * When doing UPDATE, DELETE, or SELECT FOR UPDATE/SHARE, we create a separate
  * PlanRowMark node for each non-target relation in the query.	Relations that
- * are not specified as FOR [KEY] UPDATE/SHARE are marked ROW_MARK_REFERENCE (if
- * real tables) or ROW_MARK_COPY (if not).
+ * are not specified as FOR UPDATE/SHARE are marked ROW_MARK_REFERENCE (if
+ * regular tables) or ROW_MARK_COPY (if not).
  *
  * Initially all PlanRowMarks have rti == prti and isParent == false.
  * When the planner discovers that a relation is the root of an inheritance
@@ -791,7 +811,7 @@ typedef enum RowMarkType
  *
  * The planner also adds resjunk output columns to the plan that carry
  * information sufficient to identify the locked or fetched rows.  For
- * tables (markType != ROW_MARK_COPY), these columns are named
+ * regular tables (markType != ROW_MARK_COPY), these columns are named
  *		tableoid%u			OID of table
  *		ctid%u				TID of row
  * The tableoid column is only present for an inheritance hierarchy.
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 2f9fcd575a62cd46bb939f477823a32f0379d9ad..16d685846e8655a2b7726ed04c47110e87602f28 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -79,7 +79,8 @@ extern SetOp *make_setop(SetOpCmd cmd, SetOpStrategy strategy, Plan *lefttree,
 		   long numGroups, double outputRows);
 extern Result *make_result(PlannerInfo *root, List *tlist,
 			Node *resconstantqual, Plan *subplan);
-extern ModifyTable *make_modifytable(CmdType operation, bool canSetTag,
+extern ModifyTable *make_modifytable(PlannerInfo *root,
+				 CmdType operation, bool canSetTag,
 				 List *resultRelations, List *subplans, List *returningLists,
 				 List *rowMarks, int epqParam);
 extern bool is_projection_capable_plan(Plan *plan);