[v9.3] writable foreign tables

Started by Kohei KaiGaiover 13 years ago102 messages
#1Kohei KaiGai
kaigai@kaigai.gr.jp
1 attachment(s)

Hello,

The attached patch is just a proof-of-concept of writable foreign table
feature; thus, I don't expect it getting merged at the upcoming commit
fest. The purpose of this patch is to find out the best way to support
"write stuff" in FDW.

Basic idea of this patch is to utilize "ctid" field to track an identifier of
a particular foreign-row; to be updated or deleted. It shall be moved
to the modify-stage from the scan-stage as regular table doing.
Then, newly added methods being invoked at ExecUpdate or
ExecDelete with the "pseudo ctid", so FDW shall be able to modify
the target foreign-row.
It is a responsibility of FDW extension (and DBA) to ensure each
foreign-row has a unique identifier that has 48-bits width integer
data type in maximum. In case of pgsql_fdw, "ctid" of remote table
can perform as "pseudo ctid", as is. For other RDBMS, DBA will
need to indicate which column should perform.
INSERT is simple enough; all we need to do it to carry every field
of new tuple into the remote side.

This patch adds five new methods of FdwRoutine structure.
The BeginForeignModify and EndForeignModify give a chance
to initialize / destruct a private state that can be allocated on
ResultRelInfo. As literal, ExecForeignInsert, ExecForeignDelete
and ExecForeignUpdate are invoked for each new tuple, instead
of heap_*() for regular tables. If NULL was set on them, it means
this FDW does not support these operations.

I intend FDW to set up a prepared statement that modifies
a particular remote-row being identified with pseudo-ctid,
at the BeginForeignModify(). Then, ExecForeign*() kicks
the prepared statement with given pseudo-ctid.

The patched portion at contrib/file_fdw.c does not make sense
actually. It just prints messages for each invocation.
It is just a proof-of-concept to show possibility of implementation
based on real RDBMS.

In case when file_fdw performs behalf on "ftbl".
--------------------------------
postgres=# SELECT ctid, * FROM ftbl;
ctid | a | b
--------+-----+-----
(0,1) | 101 | aaa
(0,2) | 102 | bbb
(0,3) | 103 | ccc
(0,4) | 104 | ddd
(0,5) | 105 | eee
(0,6) | 106 | fff
(0,7) | 107 | ggg
(0,8) | 108 | hhh
(0,9) | 109 | iii
(0,10) | 110 | jjj
(10 rows)
==> ctid is used to carray identifier of row; line number in this example.

postgres=# UPDATE ftbl SET a = a + 1 WHERE a > 107;
INFO: ftbl is the target relation of UPDATE
INFO: fdw_file: BeginForeignModify method
INFO: fdw_file: UPDATE (lineno = 8)
INFO: fdw_file: UPDATE (lineno = 9)
INFO: fdw_file: UPDATE (lineno = 10)
INFO: fdw_file: EndForeignModify method
UPDATE 3
postgres=# DELETE FROM ftbl WHERE a BETWEEN 103 AND 106;
INFO: ftbl is the target relation of DELETE
INFO: fdw_file: BeginForeignModify method
INFO: fdw_file: DELETE (lineno = 3)
INFO: fdw_file: DELETE (lineno = 4)
INFO: fdw_file: DELETE (lineno = 5)
INFO: fdw_file: DELETE (lineno = 6)
INFO: fdw_file: EndForeignModify method
DELETE 4
--------------------------------

This patch does not care about transaction control anyway.
According to the discussion in developer meeting at Ottawa,
I didn't include such a complex stuff in the first step.
(Probably, we can implement using XactCallback...)

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

Attachments:

pgsql-v9.3-writable-fdw-poc.v1.patchapplication/octet-stream; name=pgsql-v9.3-writable-fdw-poc.v1.patchDownload
 contrib/file_fdw/file_fdw.c            | 97 ++++++++++++++++++++++++++++++++++
 src/backend/executor/execMain.c        | 34 ++++++++++--
 src/backend/executor/nodeModifyTable.c | 65 +++++++++++++++++++++--
 src/backend/rewrite/rewriteHandler.c   |  3 +-
 src/include/foreign/fdwapi.h           | 19 +++++++
 src/include/nodes/execnodes.h          |  4 ++
 src/include/storage/itemptr.h          | 25 +++++++++
 7 files changed, 237 insertions(+), 10 deletions(-)

diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
index 7c7fedf..ef9f4b2 100644
--- a/contrib/file_fdw/file_fdw.c
+++ b/contrib/file_fdw/file_fdw.c
@@ -33,6 +33,7 @@
 #include "optimizer/var.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
+#include "utils/lsyscache.h"
 
 PG_MODULE_MAGIC;
 
@@ -96,6 +97,7 @@ typedef struct FileFdwExecutionState
 	char	   *filename;		/* file to read */
 	List	   *options;		/* merged COPY options, excluding filename */
 	CopyState	cstate;			/* state of reading file */
+	int			lineno;			/* pseudo ctid of the file */
 } FileFdwExecutionState;
 
 /*
@@ -130,6 +132,19 @@ static void fileEndForeignScan(ForeignScanState *node);
 static bool fileAnalyzeForeignTable(Relation relation,
 						AcquireSampleRowsFunc *func,
 						BlockNumber *totalpages);
+static void fileBeginForeignModify(CmdType operation,
+								   ResultRelInfo *resultRelInfo,
+								   EState *estate,
+								   Plan *subplan,
+								   int eflags);
+static void fileExecForeignInsert(ResultRelInfo *resultRelInfo,
+								  HeapTuple tuple);
+static void fileExecForeignDelete(ResultRelInfo *resultRelInfo,
+								  ItemPointer tupleid);
+static void fileExecForeignUpdate(ResultRelInfo *resultRelInfo,
+								  ItemPointer tupleid,
+								  HeapTuple tuple);
+static void fileEndForeignModify(ResultRelInfo *resultRelInfo);
 
 /*
  * Helper functions
@@ -169,6 +184,11 @@ file_fdw_handler(PG_FUNCTION_ARGS)
 	fdwroutine->ReScanForeignScan = fileReScanForeignScan;
 	fdwroutine->EndForeignScan = fileEndForeignScan;
 	fdwroutine->AnalyzeForeignTable = fileAnalyzeForeignTable;
+	fdwroutine->BeginForeignModify = fileBeginForeignModify;
+	fdwroutine->ExecForeignInsert = fileExecForeignInsert;
+	fdwroutine->ExecForeignDelete = fileExecForeignDelete;
+	fdwroutine->ExecForeignUpdate = fileExecForeignUpdate;
+	fdwroutine->EndForeignModify = fileEndForeignModify;
 
 	PG_RETURN_POINTER(fdwroutine);
 }
@@ -510,6 +530,36 @@ fileGetForeignPlan(PlannerInfo *root,
 				   List *scan_clauses)
 {
 	Index		scan_relid = baserel->relid;
+	ListCell   *cell;
+
+	/*
+	 * XXX - An evidence FDW module can know what kind of accesses are
+	 * requires on the target relation, if UPDATE, INSERT or DELETE.
+	 * It shall be also utilized to appropriate lock level on FDW
+	 * extensions that performs behalf on real RDBMS.
+	 */
+	if (root->parse->resultRelation == baserel->relid)
+	{
+		switch (root->parse->commandType)
+		{
+			case CMD_INSERT:
+				elog(INFO, "%s is the target relation of INSERT",
+					 get_rel_name(foreigntableid));
+				break;
+			case CMD_UPDATE:
+				elog(INFO, "%s is the target relation of UPDATE",
+					 get_rel_name(foreigntableid));
+				break;
+			case CMD_DELETE:
+				elog(INFO, "%s is the target relation of DELETE",
+                     get_rel_name(foreigntableid));
+				break;
+			default:
+				elog(INFO, "%s is the target relation of ??????",
+                     get_rel_name(foreigntableid));
+				break;
+		}
+	}
 
 	/*
 	 * We have no native ability to evaluate restriction clauses, so we just
@@ -598,6 +648,7 @@ fileBeginForeignScan(ForeignScanState *node, int eflags)
 	festate->filename = filename;
 	festate->options = options;
 	festate->cstate = cstate;
+	festate->lineno = 0;
 
 	node->fdw_state = (void *) festate;
 }
@@ -612,6 +663,7 @@ fileIterateForeignScan(ForeignScanState *node)
 {
 	FileFdwExecutionState *festate = (FileFdwExecutionState *) node->fdw_state;
 	TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
+	HeapTuple	tup;
 	bool		found;
 	ErrorContextCallback errcontext;
 
@@ -638,7 +690,12 @@ fileIterateForeignScan(ForeignScanState *node)
 						 slot->tts_values, slot->tts_isnull,
 						 NULL);
 	if (found)
+	{
 		ExecStoreVirtualTuple(slot);
+		tup = ExecMaterializeSlot(slot);
+		festate->lineno++;
+		PackPseudoItemPointer(&tup->t_self, festate->lineno);
+	}
 
 	/* Remove error callback. */
 	error_context_stack = errcontext.previous;
@@ -661,6 +718,7 @@ fileReScanForeignScan(ForeignScanState *node)
 									festate->filename,
 									NIL,
 									festate->options);
+	festate->lineno = 0;
 }
 
 /*
@@ -716,6 +774,45 @@ fileAnalyzeForeignTable(Relation relation,
 	return true;
 }
 
+static void
+fileBeginForeignModify(CmdType operation,
+					   ResultRelInfo *resultRelInfo,
+					   EState *estate,
+					   Plan *subplan,
+					   int eflags)
+{
+	elog(INFO, "fdw_file: BeginForeignModify method");
+}
+
+static void
+fileExecForeignInsert(ResultRelInfo *resultRelInfo, HeapTuple tuple)
+{
+	elog(INFO, "fdw_file: INSERT");
+}
+
+static void
+fileExecForeignDelete(ResultRelInfo *resultRelInfo,
+					  ItemPointer tupleid)
+{
+	elog(INFO, "fdw_file: DELETE (lineno = %lu)",
+		 (unsigned long) UnpackPseudoItemPointer(tupleid));
+}
+
+static void
+fileExecForeignUpdate(ResultRelInfo *resultRelInfo,
+					  ItemPointer tupleid,
+					  HeapTuple tuple)
+{
+	elog(INFO, "fdw_file: UPDATE (lineno = %lu)",
+		 (unsigned long) UnpackPseudoItemPointer(tupleid));
+}
+
+static void
+fileEndForeignModify(ResultRelInfo *resultRelInfo)
+{
+	elog(INFO, "fdw_file: EndForeignModify method");
+}
+
 /*
  * check_selective_binary_conversion
  *
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 440438b..7d20627 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -43,6 +43,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"
@@ -933,6 +934,7 @@ void
 CheckValidResultRel(Relation resultRel, CmdType operation)
 {
 	TriggerDesc *trigDesc = resultRel->trigdesc;
+	FdwRoutine	*fdwroutine;
 
 	switch (resultRel->rd_rel->relkind)
 	{
@@ -984,10 +986,34 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
 			}
 			break;
 		case RELKIND_FOREIGN_TABLE:
-			ereport(ERROR,
-					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("cannot change foreign table \"%s\"",
-							RelationGetRelationName(resultRel))));
+			fdwroutine = GetFdwRoutineByRelId(RelationGetRelid(resultRel));
+			switch (operation)
+			{
+				case CMD_INSERT:
+					if (!fdwroutine || !fdwroutine->ExecForeignInsert)
+						ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("cannot insert into foreign table \"%s\"",
+									RelationGetRelationName(resultRel))));
+					break;
+				case CMD_UPDATE:
+					if (!fdwroutine || !fdwroutine->ExecForeignUpdate)
+						ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("cannot update foreign table \"\"",
+									RelationGetRelationName(resultRel))));
+					break;
+				case CMD_DELETE:
+					if (!fdwroutine || !fdwroutine->ExecForeignDelete)
+						ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("cannot delete from foreign table \"\"",
+									RelationGetRelationName(resultRel))));
+					break;
+				default:
+					elog(ERROR, "unrecognized CmdType: %d", (int) operation);
+					break;
+			}
 			break;
 		default:
 			ereport(ERROR,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index a7bce75..b2fc516 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -41,6 +41,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"
@@ -224,6 +225,14 @@ ExecInsert(TupleTableSlot *slot,
 
 		newId = InvalidOid;
 	}
+	else if (resultRelInfo->ri_fdwroutine)
+	{
+		FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+		fdwroutine->ExecForeignInsert(resultRelInfo, tuple);
+
+		newId = InvalidOid;
+	}
 	else
 	{
 		/*
@@ -334,6 +343,12 @@ ExecDelete(ItemPointer tupleid,
 		if (!dodelete)			/* "do nothing" */
 			return NULL;
 	}
+	else if (resultRelInfo->ri_fdwroutine)
+	{
+		FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+		fdwroutine->ExecForeignDelete(resultRelInfo, tupleid);
+	}
 	else
 	{
 		/*
@@ -538,6 +553,12 @@ ExecUpdate(ItemPointer tupleid,
 		/* trigger might have changed tuple */
 		tuple = ExecMaterializeSlot(slot);
 	}
+	else if (resultRelInfo->ri_fdwroutine)
+	{
+		FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+		fdwroutine->ExecForeignUpdate(resultRelInfo, tupleid, tuple);
+	}
 	else
 	{
 		/*
@@ -805,10 +826,12 @@ ExecModifyTable(ModifyTableState *node)
 			 */
 			if (operation == CMD_UPDATE || operation == CMD_DELETE)
 			{
+				Relation	rel = resultRelInfo->ri_RelationDesc;
 				Datum		datum;
 				bool		isNull;
 
-				if (resultRelInfo->ri_RelationDesc->rd_rel->relkind == RELKIND_RELATION)
+				if (RelationGetForm(rel)->relkind == RELKIND_RELATION ||
+					RelationGetForm(rel)->relkind == RELKIND_FOREIGN_TABLE)
 				{
 					datum = ExecGetJunkAttribute(slot,
 												 junkfilter->jf_junkAttNo,
@@ -964,6 +987,22 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		estate->es_result_relation_info = resultRelInfo;
 		mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
 
+		/*
+		 * Also tells FDW extensions to init the plan for this result rel
+		 */
+		if (RelationGetForm(resultRelInfo->ri_RelationDesc)->relkind == RELKIND_FOREIGN_TABLE)
+		{
+			Oid		relid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+			FdwRoutine *fdwroutine = GetFdwRoutineByRelId(relid);
+
+			Assert(fdwroutine != NULL);
+			resultRelInfo->ri_fdwroutine = fdwroutine;
+			resultRelInfo->ri_fdw_state = NULL;
+
+			if (fdwroutine->BeginForeignModify)
+				fdwroutine->BeginForeignModify(operation, resultRelInfo,
+											   estate, subplan, eflags);
+		}
 		resultRelInfo++;
 		i++;
 	}
@@ -1104,21 +1143,22 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			resultRelInfo = mtstate->resultRelInfo;
 			for (i = 0; i < nplans; i++)
 			{
+				Relation	rel = resultRelInfo->ri_RelationDesc;
 				JunkFilter *j;
 
 				subplan = mtstate->mt_plans[i]->plan;
 				if (operation == CMD_INSERT || operation == CMD_UPDATE)
-					ExecCheckPlanOutput(resultRelInfo->ri_RelationDesc,
-										subplan->targetlist);
+					ExecCheckPlanOutput(rel, subplan->targetlist);
 
 				j = ExecInitJunkFilter(subplan->targetlist,
-							resultRelInfo->ri_RelationDesc->rd_att->tdhasoid,
+									   RelationGetDescr(rel)->tdhasoid,
 									   ExecInitExtraTupleSlot(estate));
 
 				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)
+					if (RelationGetForm(rel)->relkind == RELKIND_RELATION ||
+						RelationGetForm(rel)->relkind == RELKIND_FOREIGN_TABLE)
 					{
 						j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
 						if (!AttributeNumberIsValid(j->jf_junkAttNo))
@@ -1181,6 +1221,21 @@ ExecEndModifyTable(ModifyTableState *node)
 {
 	int			i;
 
+	/* Let the FDW shut dowm */
+	for (i=0; i < node->ps.state->es_num_result_relations; i++)
+	{
+		ResultRelInfo  *resultRelInfo
+			= &node->ps.state->es_result_relations[i];
+
+		if (resultRelInfo->ri_fdwroutine)
+		{
+			FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+			if (fdwroutine->EndForeignModify)
+				fdwroutine->EndForeignModify(resultRelInfo);
+		}
+	}
+
 	/*
 	 * Free the exprcontext
 	 */
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 8f75948..f461b87 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1166,7 +1166,8 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 	const char *attrname;
 	TargetEntry *tle;
 
-	if (target_relation->rd_rel->relkind == RELKIND_RELATION)
+	if (target_relation->rd_rel->relkind == RELKIND_RELATION ||
+		target_relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
 	{
 		/*
 		 * Emit CTID so that executor can find the row to update or delete.
diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
index 721cd25..252d505 100644
--- a/src/include/foreign/fdwapi.h
+++ b/src/include/foreign/fdwapi.h
@@ -59,6 +59,20 @@ typedef bool (*AnalyzeForeignTable_function) (Relation relation,
 												 AcquireSampleRowsFunc *func,
 													BlockNumber *totalpages);
 
+typedef void (*BeginForeignModify_function) (CmdType operation,
+											 ResultRelInfo *resultRelInfo,
+											 EState *estate,
+											 Plan *subplan,
+											 int eflags);
+typedef void (*ExecForeignInsert_function) (ResultRelInfo *resultRelInfo,
+											HeapTuple tuple);
+typedef void (*ExecForeignDelete_function) (ResultRelInfo *resultRelInfo,
+											ItemPointer tupleid);
+typedef void (*ExecForeignUpdate_function) (ResultRelInfo *resultRelInfo,
+											ItemPointer tupleid,
+											HeapTuple tuple);
+typedef void (*EndForeignModify_function) (ResultRelInfo *resultRelInfo);
+
 /*
  * FdwRoutine is the struct returned by a foreign-data wrapper's handler
  * function.  It provides pointers to the callback functions needed by the
@@ -90,6 +104,11 @@ typedef struct FdwRoutine
 	 * not provided.
 	 */
 	AnalyzeForeignTable_function AnalyzeForeignTable;
+	BeginForeignModify_function	BeginForeignModify;
+	ExecForeignInsert_function ExecForeignInsert;
+	ExecForeignDelete_function ExecForeignDelete;
+	ExecForeignUpdate_function ExecForeignUpdate;
+	EndForeignModify_function EndForeignModify;
 } FdwRoutine;
 
 
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index fec07b8..db5b79b 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -303,6 +303,8 @@ typedef struct JunkFilter
  *		ConstraintExprs			array of constraint-checking expr states
  *		junkFilter				for removing junk attributes from tuples
  *		projectReturning		for computing a RETURNING list
+ *		fdwroutine				FDW callbacks if foreign table
+ *		fdw_state				opaque state of FDW module, or NULL
  * ----------------
  */
 typedef struct ResultRelInfo
@@ -320,6 +322,8 @@ typedef struct ResultRelInfo
 	List	  **ri_ConstraintExprs;
 	JunkFilter *ri_junkFilter;
 	ProjectionInfo *ri_projectReturning;
+	struct FdwRoutine  *ri_fdwroutine;
+	void	   *ri_fdw_state;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/include/storage/itemptr.h b/src/include/storage/itemptr.h
index 331812b..8fdee45 100644
--- a/src/include/storage/itemptr.h
+++ b/src/include/storage/itemptr.h
@@ -135,6 +135,31 @@ typedef ItemPointerData *ItemPointer;
 	(pointer)->ip_posid = InvalidOffsetNumber \
 )
 
+/*
+ * PackPseudoItemPointer
+ *		Pack a pseudo item pointer of foreign table
+ */
+#define PackPseudoItemPointer(pointer,value)							\
+	do {																\
+		AssertMacro((value) < (1UL << (8 * (sizeof(BlockIdData) +		\
+											sizeof(OffsetNumber)))));	\
+		BlockIdSet(&((pointer)->ip_blkid),								\
+				   (value) >> (8 * sizeof(OffsetNumber)));				\
+		(pointer)->ip_posid =											\
+			(value) & ((1 << (8 * sizeof(OffsetNumber))) - 1);			\
+	} while(0)
+
+/*
+ * UnpackPseudoItemPointer
+ *		Unpack a pseudo item pointer of foreign table
+ */
+#define UnpackPseudoItemPointer(pointer)					\
+	(														\
+		(BlockIdGetBlockNumber(&(pointer)->ip_blkid)		\
+		 << (8 * sizeof(OffsetNumber)))						\
+		| ((pointer)->ip_posid)								\
+	)
+
 /* ----------------
  *		externs
  * ----------------
#2Robert Haas
robertmhaas@gmail.com
In reply to: Kohei KaiGai (#1)
Re: [v9.3] writable foreign tables

On Thu, Aug 23, 2012 at 1:10 AM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

It is a responsibility of FDW extension (and DBA) to ensure each
foreign-row has a unique identifier that has 48-bits width integer
data type in maximum.

It strikes me as incredibly short-sighted to decide that the row
identifier has to have the same format as what our existing heap AM
happens to have. I think we need to allow the row identifier to be of
any data type, and even compound. For example, the foreign side might
have no equivalent of CTID, and thus use primary key. And the primary
key might consist of an integer and a string, or some such.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

#3Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Robert Haas (#2)
Re: [v9.3] writable foreign tables

2012/8/25 Robert Haas <robertmhaas@gmail.com>:

On Thu, Aug 23, 2012 at 1:10 AM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

It is a responsibility of FDW extension (and DBA) to ensure each
foreign-row has a unique identifier that has 48-bits width integer
data type in maximum.

It strikes me as incredibly short-sighted to decide that the row
identifier has to have the same format as what our existing heap AM
happens to have. I think we need to allow the row identifier to be of
any data type, and even compound. For example, the foreign side might
have no equivalent of CTID, and thus use primary key. And the primary
key might consist of an integer and a string, or some such.

I assume it is a task of FDW extension to translate between the pseudo
ctid and the primary key in remote side.

For example, if primary key of the remote table is Text data type, an idea
is to use a hash table to track the text-formed primary being associated
with a particular 48-bits integer. The pseudo ctid shall be utilized to track
the tuple to be modified on the scan-stage, then FDW can reference the
hash table to pull-out the primary key to be provided on the prepared
statement.

Do we have some other reasonable ideas?

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

#4Albe Laurenz
laurenz.albe@wien.gv.at
In reply to: Kohei KaiGai (#3)
Re: [v9.3] writable foreign tables

Kohei KaiGai wrote:

2012/8/25 Robert Haas <robertmhaas@gmail.com>:

On Thu, Aug 23, 2012 at 1:10 AM, Kohei KaiGai <kaigai@kaigai.gr.jp>

wrote:

It is a responsibility of FDW extension (and DBA) to ensure each
foreign-row has a unique identifier that has 48-bits width integer
data type in maximum.

It strikes me as incredibly short-sighted to decide that the row
identifier has to have the same format as what our existing heap AM
happens to have. I think we need to allow the row identifier to be

of

any data type, and even compound. For example, the foreign side

might

have no equivalent of CTID, and thus use primary key. And the

primary

key might consist of an integer and a string, or some such.

I assume it is a task of FDW extension to translate between the pseudo
ctid and the primary key in remote side.

For example, if primary key of the remote table is Text data type, an

idea

is to use a hash table to track the text-formed primary being

associated

with a particular 48-bits integer. The pseudo ctid shall be utilized

to track

the tuple to be modified on the scan-stage, then FDW can reference the
hash table to pull-out the primary key to be provided on the prepared
statement.

And what if there is a hash collision? Then you would not be able to
determine which row is meant.

I agree with Robert that this should be flexible enough to cater for
all kinds of row identifiers. Oracle, for example, uses ten byte
identifiers which would give me a headache with your suggested design.

Do we have some other reasonable ideas?

Would it be too invasive to introduce a new pointer in TupleTableSlot
that is NULL for anything but virtual tuples from foreign tables?

Yours,
Laurenz Albe

#5Shigeru HANADA
shigeru.hanada@gmail.com
In reply to: Kohei KaiGai (#1)
1 attachment(s)
Re: [v9.3] writable foreign tables

Kaigai-san,

On Thu, Aug 23, 2012 at 2:10 PM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

The patched portion at contrib/file_fdw.c does not make sense
actually. It just prints messages for each invocation.
It is just a proof-of-concept to show possibility of implementation
based on real RDBMS.

Attached is a tar ball of pgsql_fdw. It's WIP and contains no
document, but it would be enough for your PoC purpose. Usage and
features are same as the last version posted for 9.2 cycle.
# I'll post finished patch in the CF-Sep.

Here are random comments for your PoC patch:

+ As Robert says, using CTID as virtual tuple identifier doesn't seem
nice when considering various FDWs for NoSQL or RDBMS. Having abstract
layer between FDWs and tuple sounds better, but implementing it by each
FDW seems useless effort. Do yo have any idea of generic mechanism for
tuple mapping?

+ Do you have any plan about deparsing local qualifiers into remote
query to avoid repeated query submission? This would improve
performance of big UPDATE, but its use case might be limited to
statements which consist of one foreign table. For this case, we can
consider pass-through mode as second way.

+ I have not read your patch closely yet, but I wonder how we can know
which column is actually updated. If we have only updated image of
tuple, we have to update all remote columns by "new" values?

--
Shigeru Hanada

Attachments:

pgsql_fdw_93.tar.gzapplication/gzip; name=pgsql_fdw_93.tar.gzDownload
�(;P��y[G�8<�J���$F���1��( lM0"��87�WO#���V���2f����Yj�E���w�'1Rw�u��u��,�=M'�<���O�����'�����G[�_���������;�<�����������G�h<�g�f^"������x���z�������s/I�������������h����h���C����F��������q[����	
D��V����\������8���,�?�:���2	��3�7��N�*$����������e�y/��v��s<�v���n�Xv;��,	N�k�o���y���=���b���9�;�<_�[����O��Y�E�7�������0>��������o���xY�T�.�~�e<�y�$��O?�1l��E��_�e���FgA4��wQ<������"�U/K_��7��X+�g�,�����5X�����[/7������U����YdA��?]aDi��0�L�����b��e��*�P ������������7�9������}����A	:�n0����Y��"n�����w�������z�$���O��up��$���z����y����0xw�a�V;��,�7����`WM���	���8\8�^�C�8��b�E����D3����3<M���BO_c�6&
at`�-N�����.��	3O�ds���7���V�����i�tq	-d~�^����|2��M�w�-����]�W��d�����0�f��ie�-}����zq���a8�K�^\��� �s8�J�h����}�]N�pG��]�u���GTX:v����q����=�fK��zI;I����t���5���WEN���E����Bk��M�+�~��E����OfA���S'����%���z�p�D�3 �q��4R�zS?�tP�� tc,�a���{�i�C:�����f5�������4�a�j��H_�3�����$�����'�8���EK/����,�Q&�"�R���r�������bx@18_�,I�^�q�C�),�l���RaS�:���x
{�����l��R(6Dr�}�A."_������q���n�K����B�L�	��0�T������'����!��9�0�/"�����{1~C��#�j2T!�������a�^~���2H�7G8�����o�(pXt��!�����0�5h�x��|��L�$En��e��bv�'5���s��#Z�=q�=�����#���j\�����X�z-�������z'7:i��^�?�bWl-�.!0\!��<^�������	At�0<b��mQ{?�������~%�B����SLq���a\�5���v���`F0�����8B0��������h�[
��9������0�\;~ut���N-���aO��b������Mp
�Q�'!��"�OFILS8����:�����[����!0�<�\�{����nS�a��;�����6Z��h8�7Q|����}�q���q1/�<���Zz���'�=`�����Z��4�����"��qqm�����}��������^�����|�y�����`��H�|����L.���7�<�� ����]tpS��90�#��F�ICR��H��g����~��;��������y���=������;/zG0�m��p,�E���~����X��9�]��(���3�3���g/$��;��V��]��hh9r���?Q�6������c����c
���o2��f��s�ijAQ0��!$A�6�	i���`��G-�����7@�PEf�i5��4�B����p
�!�6��:�tM\�krg�p�����"��x�4~�F�q�h���_��� a�1`�]��z0H�P�Tm��>�o�L���nD��^��'����b� ���P�����yD�O�Zp+�|���h�y���1Y��b,Uk6�� �D8|Y�+���X�����������,`+}�R
�0�9��a8"V���d���p)����)���RD�H���3��c�>L�`~]'�\��u1;M^l<��J��=^�%����]S[m����*���T���i��}K0kND��!��������Pd�\��.�m�Ca'�3m	�-�Q��hF�E��5@��5#�~����@�����q�������b�8z*�I�����C�J$����[���C��%�@�3�� ���ri�dX|V'd�!����8C�p�TI�_k&���v$5lM�
�:�Sd�7�	G���0��e���R2GJ�C�Wu3xLT)�|�[N��|��H���4����z�lC�5���1O�)�!{��&�AD��?���;����Dv�KeJ�`!�hq�W��5����H�pkM�E
'�)�!k�}��������vO���L��*9�2u�Tb��r���>%��_#Gl�<����'#�����.�Q��j�����MS�>X������X��`p����=�yoL\
�����D����O���c<T�����)������S/��������~��G
,"Ks���07����7�*����oc����YI9X������*�-Y��'��(����##f2(
Q�#����"��/�*G����r��G��*�F<B�����B.����H��f�n�[b�	~�oA*��"��F���H*��R}�&*�&���'.��o��-Q��b����M}�iHm������OJ<MP�x6o@Y����B{�m��������&
�a�X�O���|��ymV��5���x��	��`�IC�Y�D�b�Ac�����9�D�T����'\�a�h�pLQ����}�����"UgO �{�o�T��y�O���GVT�dxO��!:^��
Tx������H5��	�b�\
��Eu�r�v�f-������o��r��m*#Y�������!���"^c~!��u3�V�vt*��B���\Y\'���@;�t;�r�V�;����.�B�QJ�|5�������#�`#%jx�jd�XD�b>�
��'	�i�ji�-)�<��A�v2%�����N!9��>���oS��81�}��%2����������I����d+�0i7������]^���K��i#&7gL�W7g�$�&;&7�Z/�n�2G�-�Ln��[^���D��j�%�h��W7G����,�!i�������n�h���K�W�Tl�is��~��V�>�A����(4���v[���
K����F�B{�$-K���{��O
� g :jt��^_ib��f�Yc�AW�������z
����7l0�@x$�4��	�
���A�HXX���5���x�H�,?q���.]�N�'��2��eGa@�����������8<�Ir������*�4��
�i�D�%n$un�/I��A>/x��r�,G�-p�ic��~JcIQ4�5�}��DTX�GJX���a�?���ou����x����k��r������.���=�-����o�j``�Hi5J��(�9*����GXu��r�K�V��ZJ���(�J��2��X�D���2����N��D���z��Y(�}�������!�YP�[_A<����#�KJ&���&��/+�����OQ9w�������V��2�V-1�M�(�����������C�L�9jR�J��:xx��Wr����S���s���j8�j�t�����S�$����+w�/M��&�
��~�\��D�a�Fh��T�(�[)��022������xfY_�W��t|��<�����@��y��W�)��`����;����B�YG%�li�Q��8d�O`�R5�������|�[e)�*�F�]�1-�%���CT���mW4�k���b(��}�j5�CP6�����k�S������M	V���*=�����gtl����l��+��(�Q����C���~��t���9��5W��p��3k6`�~�md���3-f3���C�EJ������[����)��4Y
�eHPiV�$��W����������Y����U=1u���w>�
���X����5������BwDG��2yZ,���gH�������^Fc��x�iO*1h�k7���]/w�5F��r/��"�6x�MAp���
��f���4�=[1��q�I��9blhFA-�P�Un��l�D�`UPO���Rq�]T�i��;zJ-��'���$`�0{n�P����
���<m(�������W�����~��^������wl?f�7��b
�(���?Z���e-��	�������~���?�o�n�<<���
���r��e���W#g��9j�D����@�Ec�4m�Fm(,<-e*��R�����o�_�����p:�g�L[�;>z���qo��l2��B���{��!R����_����u���p�m�7�n�"��M@����f����T�����3+��W�3���/�����T��y>* �C�^����I�s��P����C@�6O�Z����\����j���zE2��.]�5\����yiBV�I*�X���j����[^7�^_�;M6J�'1�nH�_�She�
���\����C%P���L�B [��]"��;S�A�����O����yI�@�P�ToS�o�v���h0��z��;�4��-����4���Y���:+�������~��mS]��)"lb@�2�[����&Yx)�����BK|����c�B����q�|��5s��@��!��'.�� @OMf���a�]�%�����G���)$�:o�4���������������#��	v���L��,��nk���nIz%���9<*���x�Y7�7f!�.��p������!
3hj'�����5��*G��y������x��O�o|�5����
|��M�<���x��,����>�����J�����������`]�B�;�f[#�-9�F������-��P�lS6���9G��)-���-��j��M�������5�	�����K�Z!�t�X�_Y��Y���]%^T��$����\�K0vc�r$5k�W����"P|����;)��j'`�m��������������<*���/�rD����zb�vH�(9$�*o7��d����{�?�^���G�����f�Y��E�U��{��4���+m�c��q)u��B��G��3`��I5�^������$���@�����(�L�w��p���� �a���g��D�u7�%�Y�l�hN�"�������+��������[L��n5#�
+���O�r+e
^�U-���{J��<b�rUP���>��O�~�����b��!��$F�\�F����.>J�����FP@�%�^x�]�Z�<���7��<�g���.��M
E'���`2�y%M�-�����c%���uN�2w�|	�gUR
��Es0OO����ps,�Um`��%����Hm#6`i7�!kM�ej����1|j�U����������������;�'u����~�H����=Q>��!�/��}���J}�7i��Y��P�r��u+6����p������0��8�f���ItK�Qs]�������z04k��Q�Yk����QK^���x]b}�����bd����k���c+��a�l��8e]X�8Ae&^���x�Y��[!��M0�#�z[�Q��a[@��^�.P0&��C�8������`��c����8ho��h�I��~	P5�����Wl���L��)�/U,k�=��/u��=�������Q'AF>3�cD�_��V��GOR9����1��q3z'�~{��7{l�XR���h�o������b0~o���V8q��1���+B�T�������]�%��ld�"~)g&��%� b�;W4j;�{Gr�}yu�0�5Vx��"c�^=t,J/���)�4��}�=1 "?�L�1L�vw��=h��[��)�e���Vo�TB�Wz!�(�WO�YR�9	�AEJ���^��z��,48S,6��V�_��x���W�C��.[��U����H�A�0����!�x,�f|yS��J��[�"�w0���(����A�x�3���8�])�A�v�Y4��^L�����e	�3��h"/�"|���"����;��(��Fj�.P�hV
� �=�
v}�<���(M��4��N�lI�Q`h�������5��J�a�(v�G��$nk[��
o�����D�,�^G�N7??1^�F�B�w��S`o6��AGB��"���^��o	�~62�0�&�+��7��
���XO���h	��n������r*�7�xzU���'�/�p#�2�����'GdvsK���p�T��*���}����A�*�S�:�V?G���zK���.,_��zy����Xv���x�����zD�s��V�p��|Ak�����@�W���G��c_D�\��*�(6O!G��������t��,-L�^���B	z�`�&Y
�*d��l�Lk���/`���+���Cj{pZoT�N�ev>������_���kP)��z�f�"��u���
d@C�m<�e����r�Q(���EY_"W��p5������~�0���,�3�*]a%�Xv������c%����;p�f���Xhb0\���2��"�x�:3%W��R���V7��$|�����e��-�����:��P7j�9��1NP��Q���%)�o���+bs���xG����+�1N����t�^F�q��(Y������Zt>��,fM!`j����tv*#�������	+��Z��PY`�o����1f=5���f�6�mE�R�N�]v���h���3�m+�
����6��S�5���;dYo����eQ|�hr��,f�)�lF������LjQ~"��0��8M}vu�S{���4H���c�1�`�������M�-a����t���h�W��8N�E`-;p*�.Bn/��D���"(�����&F!���6��j���n�����V�@(	Ob�W���q���uF�wT��%c���)X�(�'T����n(�SGV�U+����`�m����u�>�S����`B�C�'J��u8,�����j# �6�^�<���H��A�q�Y�n�@�X�����R?�]ksF�@���0oR��[/p��~�/a:���'= g�1L�*1���cQqPH(�b]R�d���$��
+I��3&�P�b����*j@]��%�t��C����zJ]6�f�!����6V��1�Xqn�U��_6o��0X{s���Z�|���F:�y��8���	�t�`�4���4����Y�����%W���������1�����t����+��]����U��"���Z�CF��7��%��r�2�Ch����'.�����gs�]D�)���7��_Y��q$��$��s3�f�v���s^���q����ax.�h����Y7>�8���P����w�Z���QI
�VJ��.
�R�"�IE`�PC�}_�S�2W�oT��!	N�]���tJ�eE.YFA�M�`�"
a���������8*�)��f%��N��/un��>n���x9��.�u�|��'
p.�AB��L-/6�����B��Eo?����'xoU�	����?����z\
7`L����������`��K4#����-���7n<�����P��]"ZWY� ��[�-YM�$�$���Qh��R���[]��1�oc�{�&����-~�� �S��Q���U)w�Y���e�5���z���!3VV
T�_,Z	�5���,��7>���aBz�*�+	�=�:������FK����5��'��t��3F������@L�F��
D��t-�����E���������/��9�1��x d��;xx��|���.�����Fo��&��L��&����u��������R���u�[�������Q�pp���xWh�U����gXz6�H�G^��9�_��������
���*2d���]�*,Y%��<���*���t]�"����G�����vP6�c��>i��S����JMa��57�|�Z�V����@OA��5U%�$� @���S�������y2tMN�{�64����Z^���9,������E������18[4�A�t�qK���,"e���e<1�tz�t�>x����"�������EJb4�bQ�+�(F�kj���/���W�����%J�A�\�'�c����n�o��>U_������i-�QmT+��9��d�b���&-�1���bD���Cm������M(��*���E��QIvHM��
e�*'<�����~2FEt��^�dZx���E�4�����n+����uv&��M�]���Z���H�|V��[���^���[���<��[�s����e:6���7\��/����>�4j�B���q�(�����@�\h��K[��:\�6�&���E/%�����2@��PG���H�d�F7���X��D�}�C���y��c>N�w�����_��}�����|��x�%��g��~�wD2��P�>g�wk4X��'�jU�v��`���E���:��*,];�*�UU)���H���9~v��R.�Y����,�VH������	�s�o-6#��������}��Q��S������������M+�-����i��'C�|��{dv�-��c�},K�~��N.8h/]�w�3����r���1�� Ntj�����MU���X�D���m��>�R�C�xc�
QfP*��>��������N���[�Wm$���+��5����`�,�N����[����\�'���xUb�N~��j�� �y	Z��������!{���:)�@�!�*����������V�����E����nJ/�Sj�U��G���=U��8������Y�^Z��X��Ux����`�Cx�����S�����9Jk�������q��G��L�*��QJ���`�� �����C*lw���>��4��`�ba�EP��y���Z�/`�V)+�f��K��V������T�p��%��$Y�4
�+��{0E���z^
:4e�Vi��`�u��r��I@_�p6o6�JP��1������c7�G�$\(�J�r�]��8^D+B�J����DM��UW�3�'�{��_	~��E���(~^��q����e����g�Mr�&oW�g0���f�o�������RFm�F�Q�9���F�*����L�=�H�.\��J��<�~��$�y�e���(>���3*}�Gm�K�E�n�2��f�4�����q����N6��(a�Z�ym��y������@��`���~���X��m�S�}r��������bC��}��w��c.Ua��������e�H��W$t{�:6��'&���`�z�$�����o#�����y@F���	�Z��=Nf�yM������*�`��^6�H+����c��)��4�g���9���^�d#TKnR��\;
�/�UJ
r�H(�<	R	98��|��1����N�s�m���:7@�\��3�S���*���`=�������E��%ABf6��������d
��'I���=qD�pM���TE �7Z?��-
2Ky&#�`tU�����(E�'���:��eO���d�(�@��:d�@�f����j��sT�>k:�d�.��a����Zy$4�`�P|��;��~����Q�`�;�cs�~��i���E?�+����!l(�+�v4�\���|��R	p����d�l@)���
������&����wa0�%r�S �YaU"�P��������{�S�^����_��G?����#|����rU����FdQ�u� `9]J���,A�#���N�A��r�
�z��c�Q�_���+m�.�c�7M��`eVr����s���aI�3���cT�������7�^�#���?G�	��7d`g4����?D��%7�nGcc2��}���))�����uD�2���Vd(����	[�����.����c������y�pj���5��N\j
�/���fDL���S���UO��v�|���`����1�������������l�0��'���r�|=��=~����9>�8g�^����~Z�������1���yh������lllonm��[�������d;w�=~�������wK��E8 ~c��@v9��A����Gs(��O�q�"�6��~��v��8������<��dk�w��_c[�7��w�����AV��ZJ+aY��B`�h�Y��i�=�/{��:����Je������W���/�~w����A���_[��z#�kwS�2]��x�>>8���	Ar&��)$)�l�=c�0�#������}���Lk%[J�/H�����`��e����:��!��6�`�r
���w�������du`�R��A���?�M�-�����O�)u�B��o��z9��n~F��&���`�0e�X��<rl��
h��j?��uq�F*jY�W@����n��������������t��^�aO��a�>t4������C�-���-�q���~� O���������}�/��[�~9��=Gd��8aT�Y��rC�b�
����[O^��O0����~T��7���U�����s������;
�r(�2ap)�]�J-����Q�HE3����T'�/�5��(�)�� ���/��O���Ig%���J����������'_������2��+eD��d`�%�@��h$�-_XH�QI�^��F��4r�z}O�O�M������M����z�e���p�:�/���}����{���}�}\u�>6��O=$����_�������-R`P�K	�4@��������f=�����N����`�.��''�G��hj��u�-6i2����^u�u�����e�^7Z!���X���^�w��;��S�:�v��cJu�����{K��D�uC�k�������YW��|=h�?��������E������'OU���<y�7��v�Q��?~����:�����X���������?������?�gcC�}��L�����^|�~{������Mt9��,����+��"�y��`1'�f����w��?�Q�M��/���w�01��}�WQ��=��'I��������uD�Pt^w���)E�yPEK�J�L
���Q�y�X^�t���K�!C��~5|��w���b�����-���w�c��$��A�}��������
vw������������}���-�_.��^���z����zK�� �����%�Om7o�a��i�-��x����6~�9f��N��2B����8f6�������x��D������~�'B��7K��>���	4�{G�^��V�\v��s�)���q���Gw�T:@\���:<�/:/�bm �����4������Y��/�i/�p��a��=�l{4�_��~�e�������h�x`��F�C�.9@��������w�{��a���>�c��n�{03�T�=�E�&�ThM�I�W0���-� �G4a4��$^c�i��o������6���������h4ds[k]L��t������se3�k�0x��^#�����A#^�����
������Z����[o������d��d�x���/���<��<w���o����3C�����1�l0����O��9s�����x���6N�\�j	s��
��!�����n7���A��X1q�~(�.�G��m�
������NM[�5yy�z��Mi�Q�^?�WL�����#���~nl�..Z��������<��t���7h�2��J����7�8;]Zi��s��}�w�rE}gy����@]��ULt�8�V�;F�����g�}`�ts�J��y5>5���3D7��q��������lK0��B$��p	�$�cKd�FD��y��S�w��N��>PX^��t��S����Q�+	�6������I�����1FKn����������*F[��
�,"�+?��eyJj6�������Tt�;�����7�y���o���[�lW���}�%����`+����t��b��"���'+������r������\�9����k)�����{*����%�</���y������6�AU�� �Q���4�6�{����n�/��;4T<[����n�[��R������L��j
�����zc[$�E��;�^o��z��)���uXf�-%�~�f���s���W�|��]~P�--����>y�hx�e���2P��z/qO���<�l^�d=��z� �	G��,'��\��6oJD�<^�"/��
9L��0���DNe2�pE�&�K�~��L�~3���S�Ab��9���A(jK{*�!�����k�eI�V�9��\����]� �C�;�l�� �)&5�T�������,����V��{%3���I^����cW�B,�h���;7�o��t�i��)�^�����3L���*0��}�= N/��T~��:��zS���WSHT������b��ER��!Z/����Q�e����i�����Y@��+v���|��� ��n<��e�������S9K��J�����*pY�3��������j
����������#��1�����6{4)�����133~��v^������{���{�����.y}���S@l'�rA���_��[;��)�'� #N�e��Z�mM�:-��l����?(m���{h���Z��?�����"���[�z�Z���b�e���e,�?*m���{h���Z��?���������K[�|�Z���	�����O���?)m���{h���Z��?���������O���^��V�������G��?6��c9D�_��V���`����������Yx�Z�L���Xlq��C����R�,����Zo@{W�JEl$�mo"�?_�	���)�5��i�BZ���/��s�b��<���K��G�Q�GI5�S2F`?R�pM��e��v����sr����
�-��1��Z����g�`%����zr�����O��/!��A���S������]�������������I���;V���&D�I �h4���O�����4
R��n<XJ�ow_�{%}�6E@��Y��y���~���x����w�A�I�B�>���b�y_�J�&��m����~��
�����v�o��L\�,q.�`@���*?�QDJ��s�8���W����R�Wy�J�l$b��"�ZSo;���'�Ga���m����6�n����u����x����5��jn�6�),Ns��9Ma1�[��h
����m>SXl��������I�L����l����i|��M����� ����n�m�?4��oC���m0Q%(�v�?u�/p�������
E:� ����y�r�^:����.!��6M�.��.��.2�.~�.��.�.b�.��.��.F�!������Y���-�_S�p��X��8������j�a�l������,���D�/����$�w�6�(k
�o����k��##�	,�5�t����Z�p|����/_����w��4z���G�CL�Kl���}�"��������E�����9�V�N�Ml��w��G_���g���xwY��N�
�}U9�v��$��T`�#�Z��MW��V����0zX�?�Z����V��3l��������~�S��Kn������������
$<�)<����"=��"���o��6WQ`(?zIK���1�����i����3C����o9�P)}w(5n�wq�~������
4�(�� bkK�
�;U��>nh�J4x9^�������	L|����;(��V�B�`P7���7=���~�-�K�q����h� n��V�	&l�!���Uo�qK?J��>� ,������9F
mm*~�4Z�0�G�=�1��-�#�k����[��?��	�q���F���H��b���}�@E��P!n���@�>�>��j����@�p��&�8���c�@��%�FiD����m���|�(f�d�n�c!*�>������?��C���$����0��K������m��}o�?	�,��	���p�}I�{(=�z3�4�l`S]n����6�������O�$��OC��2�>'C#��j�7�����s/�f~��?���������mW?�wN��i����� �f{������V�
|-+w���*�b�ug������>�s@���{]�)F�;����=s��������_�6����#���CCX���F��G3;��M���U����{z���	�5.NS[r��������q����/#C�t�\yk�a|�S��h�hZ�t��;��:�o��i�JN�����������%�Q��8��u����E����QVV��Mf��B�������Kr�������s�/D�z
�h?�&O������z`����b�
�[Lj��
���z�������
&*~�[�c������*AKA����x�Gs���C�,U/���po�=C�"���"��O�_�[|�v���
�����u����a�`6[� �%�������?�M���������'���s%!��T1�gA�(�/�a��d������VF��Cv>+3,��4�[/����JSN	���"�y������<h���=��/���RO?���T���z]�e��"�����P5=�!F���8�mE���I(*,�&s�7�p_��n�����|��/{�eO����=�������_o��fcLs���:����uJ1^��{'��A����L��n��CN
��7��+���������&�j�G�h��~�Ag�8�z��,$S��*
�6���"�K�{�:�a0�a�{��bY�*����4 o<����[v�qW]��E�m��������� ��%�L�B��+O�o3/��|�[����l5fK�V��;4cHq]U�"�T�<�(Hl��!�E�C2�'A*�o@��
*��i0i��y�g�X��e
Ua���}_��G��{]��eV�
_u;���V���L��&C[U^�Gb��I����X|_�2
M��H/��{G��0j�j��t�?s�~���;v^c��n�����0�����x�9]�f����������O�����I��doM|��/���J����d�Z��F+�AJ�.�b�U���[x��ag��B�����O����������AA�^
]�r���s���e��|/kT`�~�=�����4�������}���S)��9��*��;�������#��	c7������7x��o�bLz�(���xB"
�u����BU~�%��8�������}}���.���/u�� ���n�6���[U�@�Ex���O,�	��|���K/��@�*G�'B��������E�W
���"	un�������:��R_-+�$R�,a'(7��{��~�u���no��"�����������J2�k*�����	������*C{���8����P����Y�)-L�`���U�r��B��qe������v@�:�v�[�1�����9F�������`^����'����Fl{kg�o�;[���<x�h�o[����<�����1T~ o%B������W]����������5>uq�6��ZM�gfQ��Y�$N8_�~<�L����P�I���$N����p$��������sj�y2�f���a����F(G!���}��
�����_�{��������n�����%���Ds|c^HV�:�$}�K�������h���a��������Z�.0�u�J��Y�kz�az���9e/NA�QZ��<J?>/{5�g����X��I��e�y���AnQ�����l6���6��!�u`.�)���`�n�����kr)�0���� Mc� �j�OE�%�qFM�c�V���^���:��p8�K�A_�v�Lj�a�Lv�x�����f��H���U��PL$q>��8��({�%a���8�#�F*%LK�E}W�}$�4�z��O�h�s�m9����
�]��esc�4�P�Cnuv����k
���"H�\4�7�j�Y�Kf���kaK��Q���������{Y�i�Y�F.4�5�?�?��&e���x1l'�(�q�$��a;Fxb��YLIEao1S���.��^f~o����$�F��"�}���	��/��o���0�����fA�:��GH_�s�H���3��e�OK�*�)oHCZe^����E	 _J~�.�i��?���h�@g�aw�+����.*������p*���*��:���0{�:L��0G��<�S�)e�$�%o��;���^J������>R���Z�����T��NP�����������%?����C�=t�)�9����$��Kl�0O<�g}���qH�!��*S�
&���`�����-�E��z���."��i�/�+a�U��zO�1�s�3��"K���
�h���|��Q��p�'~�h������0;'KKXB�A��za�{�K{��N�(`�|��2�����66��H�?�P+����s�]�Qo�y
�@:�p�>mb<�d�Sh� b1
a�+������=x�?<���Y��;�0���{A���P��6��uT{��~�����VK�0�x���M8���&�����z�C�=��*J��vD����y:����3/�s`��he�#�-��q�q�pd�"rk0j�4����t��1��4&�~�\�sl5�����1\�7�f��ZK<�a\�����y�6�u^���]����b1��E_��lI��;?��N~nJ����E��P���6U)����o60��Mnf�����N���!�#��U}XBC�����>G��a��l�H	[�
D ���f�V���"Ok�L��I=*�B6�O�a����O��g�7��P�!=��I%t'�&�:��a���j"yX�$b�r���������\�
pL�b�P��V=��)�6\:�>7dv6U���KSv�d1�6��A<r	�,���MK�Z2����	!>\ODN_�/FF���3I��T:+�y^��hnf�������:��w���B���T-�5�sX@��s[!�����$�i<�[
�L`_�Kb�W���A�:���{i�q��_Xd"�.1&t;�������u�S�N�����{k7�Bh�g��(��Gn��6�68��E���rOz�-d`��h��Y�6}C�G�Vn��K�5����u���cn�f�>R������1B�2QAIe+�h������MYy��L�@IT����N}�:[�	"Y�>���u�dM�^��VS��r
`��d�=�������P��<R}z��Q�^O~�P`�35Y}���\���a�;���~���=�����S�.���4����^=�k�+e�Q|3')��o�T��Zm�r��"��FHzS���M��x����:�Oah���v�_�s�3��ajK�QCc..����'�C����8.�����NQT9��<�yt�q�YLMsNx��[9H��H��d~xi#	@w�n��6��Z^���a�V�k���"�����P�!��h#]��N����
������Kbh��"N&�5dLX��e���z)�I<���hkcBX�n���`�y@�-l���AG�k	?������xXJM�$J�/���%R��u��<����+4�H2ic�q�Q��.�4j�%�������+R��bI8%�#�R���]?i4q�y�d����!�����D8��^kl
�w#���j�����$���{�$Q�Yl}�*	�C5��D��Bh�	��_����o��H�P	A��7� ��EIL�q<���~��3�������{���;������������=�/'��������"^�5�H�^6���
fU�<�������6�R�nb�E:9�M�#46^�.qV�����%j�\w�{3R�V���������!������o�`��	�VP�(��3?����
�����.��Zp��x��JM�y|7�2I��qw�
$��(��)���4�
g?���`L�a�+l�����W�,�yAw��D���i
`���y��P(V7���K�P�]�(T�@�bur�gb���sF�*�N0=?���FL����w�pQ��Z�����	k��2��f��������,�f�"�_S����n�0�����0
�J+A��0x�?��[��=���H2b����04�TR=^������"���3S��:Y�����m�.�K�+fd�e���*5���D���j��7��]
�.~�u�/C.O��(����b����E�����kz�����G��������F#��f/�L�"@&�n�����W�s?;���#�xYa�6f����������7�����DE	0ay�+�C��Q���R�j#�[VB��������0��V����=�B�� ~)j"���;>���l����{P��������	�:F����7���� ���6��Eq��TM�x��{����kk���Hc�*��!�I}/�c��|�1r+���l�F�)`s�M�~����G��S)����G������S�`�'P'z��x�����5�$�K�.��i������Y���$�
H=�dL�Ko��Pw5��
SN������?-�+��E��+
P� �����\^� Q���W��p��Ts�.P8�$b
���e�"=A�2VY"���9pT��!3\W���HL'�(��mHg;�D2��d�D��$&�Z�@Rf����B�R�b	3���$&#�
��P�
����g�����"��V��f��������F�Mc��J�@��x������`�J��|Z|[�����[4T_�x�k'������6+����y�����#a*��Fj����t2�������b���`��5?����G�>b��
s[�T|3,�,��(Ii���+~4u���%�q�|��RgD������[<`j?��<�w�G���|�Q��P1��R�4���y*��5M3������Z����]7j�|����i��]����O�l����ec�������@	���Q?�*ci g���;���
����2P+qaH-<��@�������wO�y�s�yG���b����G��4
P�yA�4|o2��b'�@
����M4��q�DL����	��u�>BMx��Yf}�8G�A��lE9+.
7�%�9���MK���N�bJ}���"������]���w�um7h|�D���q"��y �Fg!��1����T,��e�;���9��d�Ku��=D�	����	��d��.���A��r|�#��f��<��s�,�p��i�q��A!��,��e��K�I�]�VGy)2�hRc4��a��E��#��_Y8])gXK	��Mi���M/�	d�8D�2���J���D�'�O��%����F�;�I��J�1�������,N��rm	�;�
<�8�����W���D@X�M�R��rN~ =���s����?�����cR����!&7�����)�z����b����R5���<�2����WD*�#U�o��,W�:�����{�:_����^����g����pW�m����AD
2$~D�Z�����4&��,��Tb`^j&B��38W.�5�_����E����]�/u�r����2B�"R�H&�2q�"xf���i����f��������l����=���F�W����Qb����e<N)�#��r�Z�
#I���K�-��A,��L��AO���mW�-*��4� gE^������������E"��>���z�?�����tw�i:��������T
�i�q�r��~*���J�Z3�vF'�Z"��b�"?x��j��S����B����H-����y]5�3����|<�i����!m�������+�C6���3��%��{���S{>_@.�r	�9�)�y���P�����+}`��R��Z9�T�;���O����hS?����f�u ��~n>��%�t��l.�jQ[t�p	a��-8�}�����D���cky������l�lI������r��?�p�G�������Fex1��_���}����u�i�B>p����yGf�1��eB	���D�9���ZN���N?._��u��,�9���=&U��O��b�$��	�4���%�3���(��� ���}^�96�2���zp�9�B<��rp���Ht�W8L�8e�I���sB�b�����
�h M����*q7T��,���e#hcDm�fT��4r�ph4C6�������!������-n�(
�~Zk�^��k��mi,YVq�z2TTY��Z[m-����#V�����A���,N��4+6����6����'%n�_9^�+b��C��@!4�M�;�����(D�0��^��,����'qh���O����#��(���F>�����]);b��d�����##��L������!�7f����t��������l|r���&�#mEw�&��L�V���fJ��^V�����
��q������:c������3z`�2�Rmn�:y�E���g->U����}lG��/q�U���bd�����J�@�h�c^�V��)p��N/�j:���Rh+������[B��5�$Y^��wDQy�|��3�#�<�BUJ_p�`�<	�;?����*y�H])"
U0��L�|�����:������y�{<xux��'�}y�&I��K��n��lX�UQl�j/�a������,1�?i��-}��Am��t?(�a ���"�/A��"���3:i��go��#$E
����-�wL�wK�
�k��8�T^���8��s\n�Q��
�*����/��e�~}���_c?�?:�����8�_���O�Fyy��QE�����<x������O0����x��������v�X�g��qn�oo?���s|n1��Z�����v��6T�<0A�C��9�%�H�d��`�'J�5L��?"��<Ij��8���3Tn�3�u�aO��:r}�n������.��G��'W�����KBu�������:��_
:�]��3�hh�W��~���h���n.��f^B�?v�&���K|���O��	�mW,��sE����@4&�t1b]�1��~�7��p��%4�=~N�~9�Za� 1��i��It[oL���[�������A�67���^����#pq�2x�Sx�@Pj1���w������1H�	��5��P?�W�Y2�����*��V�dv����)N����DU2�y/����rz;���L�����#�~���_�p���6F��Gs��{���l��w~�ta��F�C�.�9@��������
�=��:�L����AR������+Y��A��������-���1��_?|Il�z��_�!��1�YMqO4���&�����w��7�>%{& �+��^#�����<��X��E]?���[�,������Z����[o���8�����%C��8��xP1�E���)c�|�T�T����8��q����'
[�Mr������S�����5�N�9�z#�Va�a��X$Y��zX���S�-E�5u�<Q~��hY5o������������������c�e���K�$4O�W��f+/Y��O����>����HC�Kk#��LCt�W<��b�5���,<A��<H�8+'�S����w��9�%�����Z9�ts�J����������'GT��B�W������������H����G��W��:�'P��f57�q���U����]97����o��8���_�*+7���q�M������MZ�������j��F,@sg�����rl	/����,�=&H����(%V����r:�Tm�1��QZ��L!��`�WV�#��Fe�
����
!���U}���)q1��J+�~�o���O�3zP1��4���@>>�J���{u<l�m��S��0�8��'�JUy����z0u��-z���|[�"�n^s/��)��_1��[�`{�J� tR�oqa�n"��{/����gOw��M��b�*����S��;|�������Qr�j�q�Z�g��<�?�j/��:��.7i��1�Z�w���������VrF����E�"g|_'�S
��t%��8��Lc���F[l���,����|��0���D����s��]6��Q���E��I���9xS�����9��	�������s��+���k!1ww�e��;��������0� n��q��Ez.&������9����=�"{���y�8m�8�����-��w8�t�]������\����x�C�&���I���G����������x���C@'7����o��a����7�������^�`r��26��Aw0������$A��d?bY��?7��~��_��lovOl���^�%m���[��if����nA/~m���+�O-���m����~
N�Xtd~�'�EnM�m+0��������k���������J�F�#h��v��i���#C��2���W�`��S���?!<��f���M��}��l%���Z���a��?l@�\r����Ku����a���)}x�2���d�����?h��d��-I��x�`�Wi�H�x�z����c��e����������Y����n1��������Kj���Q��S^�d���P�4;����7M�z����-��&�voc��P�A����w�v����2'���1o�����2�����r������?>y�����s|n?��D2������r�E��1j�� �QL��r}�,���o=	d���4�\{'�1����O������yV�.r���Q<�1a"��+O�rD��$v���8���p{�w���������n�tW�����==��C?�����E���W�JK�rsz3����{I��(��|2 =����1��.t��t1��y��O�V8:a�7}?�����������#$b:�����u�'���OF��~9\����.(��~�M���IjF����i�k����wwW����oH�W��d�r���^��l�qQ=���V+�
�E��&���������f����{�����E��%��g�|J�?�����������$�����,�N���L��q<��SIc�(��} ��/�&�^>�~��/�
VerQHF]���"�42(��?~r�W�n�e���T��8+L���p,I0�X0��l���f����:�Q��������WG��������I�fnr/�C�?�m����-<�d�d�����T��lommn��%�����M�|�.���C�����>`�������Z
����������0�z�a���x`�
�DZ'sS�� �7��)gc����� ���

0'Vq��0��Y�O����y�������?<�SNc����P��o�{ak3A�CoRK���]|��aZ���k�y��1S0�43O�8#6��!�W���
�����E���.�8A�n<c&{���#)
���RP��n�Cqi���������|�����RtZH2(���8���yo�$��S��a����.N�����P��N���K0m^v�������rBf�{DmGN����� �N�������vS�V�P
1E&�@�@�$�	peT�xN���QO<~Z�f����Z�b�E&�\��c.R:�(;o4q�����G�zX���p��2�������-k4�<�HM:Zec��$��P�p���fZ��l��,k`�B]��co����b������������2d20�6	y�)�6P�5�r$�A%:�8B�PBLv9��Z2�����&qZ:�A��Im�HF0�(��f8�}��6�q9����0!
�0�����M���!Pt(�`��$8#���X��e���:�c��d� �����V�h��f1���L�� y49�x�-#�NO������R	����������t�}a8k���d�.�C�w�hCO�#IR��������9&/�$10�����y�����Hd�������.�-��T\�U���^��GG�.� �`^���3]����l��2�Y1^m�h�.�U;IO����l�6���O�AGv�k����	^v,'��u#��~*�MN�Z[Lzrl5�rR�4lN�������Pq>��uBY��pcw��Y}�.G���g��Pv�;^q��N0��_)R�����k���
�J[P���0]�,���s���H�>�K�<�S�*�6�0�TX����DosU��)�8s��E�o�y�4>���@�#L��h�����d�@]��xQX
�
�>������R�?�|�%�2�@T���Q~r�`h�bs��7L��$�3�i>�t��P��e�o����R��1��[�+	K�\����,��b�_�qY�����)vl��f���P`�`�u��\�1S���e��a��5�������vh	YT>+o�;��Jch���wf5C����Agr��	�W�Y6����nV�M�S�s����]�6�k�D{L�^���#�
8A��.�F#g����9�[1������A����IJ�Hp����2��L;�y���	�y��y�"���I:�����gd���{��Go����:$�q�%��+�g�����AG��1��8#v�T�-�D��9�L��8[������D-V��A�D�F
�7L�~]W���*K��rh�Hn���f����T0E�����&XB�F%�	2*�9]&��H0��.,���6���R�]��L|����*�T<Y���y2
$���@��G���	�>W�tnW>,3.Hb���T�����	�8��.>�,t+�j}.e`��\�vW6#���{�c�O�Z����d��a�t
���gE��^��[Z���bz\Q�N�
!�F����R�)����I���n�)=U����W�w�Z��33���Y9O�.��^��%DP5\����I���~#��B9 ���#)�%j������K�R�Tb������R!���dq`b-����i��a��K
5l�p0=��1��(5�����%<��i�I}��q]�E$g[����)������!�g�-�s�� ����j-�O�S���Zf�,�`�^��ZK_|��%�/%�g��u�xS
Fj�]%�f��!�c6+�)6/f��R*��C�D��a^�{8���X�lT�������*�f���\S�Y*��2���`(�;���k2j��q]sA�JRO*v�T
A�4"7	��J?P��s����=������l)����0����[����4Qf�p���1�L��n ����[*5{�'JD�d�l[�����L&e��X�F���-���������BT+�������L"X�,wZ��)����Bn�z
����Khj�����;o�5��Q� v��<7��'5��yQ3"�qN}�,swc�FJ�����d�i��wdZ���8�Q��}�/������1*���*�`2P��P%|bO8��X6Q ��W#$!Bw74q��=w\��Z���3�k1WCz��)�����a�G^�����G-��-��P���$�Nr��uK�4��F�dL�`�����W�$�;�(w���8�[��`U���Z��&����E�@@Qr]4�k����2y���(���8�#��V��S�E)��j���C4�����}$I��s7Y���Y��*sV�D+j��"T#�����@�h� ��d[$W�{�V	�8�\T��������,��>X>"����]�~w�ju���ln�<8M>��_4f�=>��[���j�[�����28�=��D��K�Z�����NPl�e���Y#XVO�ZR���LrM6�D����c����r��ud1g��#�b�(c��e�ErC�Cs9�Cr�5��$���2sRR�Rz[l;�������|[��B�'I)2{�<N��'AbN"+���<�y�R6u��{�zm�^��)����8B�����t�Y@F�d�_�q������Dn2,�Lev����� MJH� :�a��4[L���/F2��-L�����,BA��N��e�1]1�����L����_�$�|.f����)��Pr���KJq8Sn�&Ldh�M�6���A��=M�[�\��w��\��v(��r��.����`�*�L�q9G\��s:g�����d���4@�h�Z0���y�M�[�D3�s���;��$�l,������b2�c�@E�
�
��M3���@�Od�3�9�vOwV^�d��79��6���o�)W�a!k�1�~�-�b��R%���0H�.���jc����-l�L��f����n�M`M���+�����G�2�)��y!�SDO�e	xG`h�D�����e9�#���>d{�H�/J-<_��c2�C�M�G����������kN��V��r��,����hY��C_.^�_MmB�kib�(�:	@�K�0_Z���$�H�c-��e6�j�Si�\j�,�;KL��K������2G��A.�WE�J�d��P�����}?d��p\��0T24�OrK���=�'a8���h���w�.�a6�E<�}.3�b�}��%����_��������[��+D9�^:���5�qCC�^B����3D/�����������/w�Uo�Ss�p��5��D*���Ja�\U)���K�\i>R��d��4�4��%D�qq�.qE��1a,����Mt�����;�:U�K���E�������f�~�C������e%��pGp��:�|�g��	(�F�,������pL1&j�;Yj�A'-:��<���8&oT��[��1����������^���
ybAeeA(yI�>��hL
(�-7�fQ��J�$��cg?1��K	��8��'IL:��";2_���R��
X� S��[���=�j��d�p�m� NL�9���D6������?zacu7s�Or��<s?�.��+�����ZK���<M����0i��d���<�����{m�gfa�o�FdS|���W����L����mZ��L)��gv2MG8*�%,$x�l��Z�Lt��}���v��
�����|
��"2�vzJ^!q�B�����G��Q���g$@|\�1�{�F�(����~���/}SZO����l��f��a�����N�VjyQ�����%t)]�1�%�i@
q,�=5���:~*�w��9�y*�.���\9��6�G�������e�x����U�����*�^�_c1g����O����������B^��to�Z��Rp��f� �
?�5��#��n
���������z����Q��
2��*��Se���>�mo�����u�J�'�
.`1N�I�5�8��.d�t���6�_�5SR��g<�v�z�4�b���'?�N���r��2�6	��0���Y*�rX�|����q{�"�j:u���P������@m=vX}���J��U?�.]�sZ+�v�����+��uK������������ur#G��g@��Sq�{u|@Su���_�$#MJ�a�@y���P��}<R����,�A�	@�%�Mg�5QjSZC��G������!Z�NT]��?��;��)� �sy�T�q8f��TE/���+t��=W7n�2������!��K�"��Ww�
|P��u��gK>����M�H�"}�A��w6���_c$���jV<��"���Y������g\���=G,�v���(I|!EM�1������|��:+C8{����T�vg����<�!�tr�XB�5��Z�x��t�h��t
�U�w��o�c��3�5d�����s����R�@�sE
�&�p6����z�^)3NL:����<�2�5c����h�r�1Q�&>�*���.Y�!�d��yg`5�����{s����{��C"�*�t�-6�;eet�J�>��H;�R�d*Aap��'H�C��a�@�4��sl��@�M�{��^z�;c�*�62��������RK��Hy5���`.���f8m�I*��1�4o��@��3D
qf(s�D�'.��!U�a`�I��Hy�j��8��g/4���5��'�5�A���?*��b��
: �����Q�T��	�����a=�����q��q��2?�zMj�O�8v�ME��xB9�=�����$El�����/�5]�E�<�Z4�f������kj@u�$���W�7H[�%��5��t ����)�
���w�nc�;I+���A(4X����TOi"&��*g��K���2�/�.��I�6VA��#�K���
(�������s����d�/�q-t��Mwn���2�I�2��J��>��V��+~Z�3��]w�{�t�����g����d���76E�x������0����[2z��5k�����Q�?��<�DQKx<�x�|�Y�*X�J�2�������J�U1�.�����G������U	���c������B1p`����\��"JZ�J�(�
����+���������Q��m���#P�U�sv%�[:����z!�|�T���dur��0rQ ;W�EK���IYu�|W�Tg��X���
�X
��|�6e����3��'��%:�7��.��7[�v�lz�w�d�6�G��y�3
_������������6 Y�"9:.4(����s����6���"��yj��w1WF`kM�k�'~�ac����W_"�v��O�]�V&��R���6w�)��F�pu0�,���������(S�Ed2�>���:(5����B���
v�����R��"��&=A�9_.;�N��
����y�p��l��!%
,���u��s7�d���kIcP�����t��&�����1�E�#������&�nn�?������t��IsM�uO��$�}��m���8T+(KX�Ek�Na�w��b�"w5�k��b�=����[�`DH�`�/��s�J�+������7�����h����������C��s���$v.�C�#��.P!}�{�T{Z�P��A�AXD����^��4�Ca&�w���-����Y)�,f�Io7���~���Kn\��gE��
qF+��b��>X�//Q�O�c\)�r����G2zi���T��x�`U2?Q��v��k1�?����}^���6�����@}*m�d^uU�����*���Z�y�Wb�����)�]S-S��k=�R�j)'bt��pQ�^�P�����D}����a�}S���q�R5H]�^ExYt7����29�F��%�s��8<�AQ��(`�����|&��o���B����;�3.���d1�]j��� @�����Q�����7��G�o��K`���o�����k�������5N��o��~�������Cqy�c�;Ofx�O���K�+5��9Oq�o0����ItC�!������c�7��
�Q7�e�t�������tGA�-�>��|o
���G��xx��4��K��m�i8�4o�*��]20�E��3z~�����-����'���a�5Xw��F��SXV	����s
��hH���^2z��2�����b�V�6S�)��9�1F!��H^� (
����k��3r�aa^@�7��{4[T?-q�n~���T����e���U�a����N��������-^	*��>0�F�"�
�%���l�l���_�3J�J;�=6���.B��Cg�&�)ja��5��;B�"B���i�D!���3�p�h�P���5��34��I|��>�*A��c���0BF�>�ao�SBz��.H;�We�q��j�/*]��as�y=�u,#��J8Z��	p�8�GUG���I�F���=i3S�,����+�7����Os�,��<��G�#,Ux���U������wn~��3��b2�34�n4�>-�@���-�������lM1�Z����v�k_c��z5�������w���@w�S�{*�MH3'^�"��Q�I�����������N�O8���j%O��������^D�E��N����pJ�r�N88:}���9qu�p�05�&�5jkn��[��L)���2������f,T{�t��>a����������OV��+��F���J���BR�/�z&�DA���a�sS�eL����5Y[(\�!N��m��|����� ��d�k���Z1I�����a[������� m�Q��kc����j�B��`M��f����_�;�?��p���r��oU�TkO[?��7�z��M�J�:�`����]��,��3�Qj|�'�A���Z�t�Vl��y:�$ze���Ea�I������$�<������*�E�S�A��6�(=c�'��T��Y���������0w��`�C���o:<���VX%;D4��&����<��F3���g#	�"����Gv���\W}Q���8���:|��]�V9�r�c����kl��6f��5lQ4
�p�X~w��3�Un���(�����s��R��;�"#�sNe�L4��{��H�`�v�����7=���kj�X�_9�gbeO��r9����<x��;����6����^"���0�ox�0�-����5����X@���<�t�LU-e�D|DN���fD�'���3�!BB��h�Ni��������iVo���]����}���!���*2�y�u�I*/�)��%�j�K����l$�$����L����k8����-By��^�s8�
�+#�\�����X�q��O�W��=�������X)����2��E��u�������_e�T��>`��8���b4��D;]���)YI�������2qw��8o��4P���:�L����L���T���B��s:�t�;������#�*�O����d1������b��B�Ro^������z��oW!���~b�w���������2����Ds�8� ��:��Fd`�2�T9�t$+�s�K����H�}��.
3�`���Cm���E�e��� ���e�|V}P+0�u��B��|��b���Fu�X�T���y�S�s�����-������H����Nq��������*^rf{�����/���B��	�.�@6�4���{���b������r�l����`��W�Y��Zt�����5k���R@�g��4�.��U���i}��dJ�x��+�^�.%k���o�B�7Q�[��r>������sv��T��-wU<������xY���))q��e��r��/��k�A����g���5-�[F���+���=X���e##@/��u��S�����`.?hv][[d�����c�M>�b� k�}�����<���$h���M
��dA+6v�}ry0r�t���|����gM���b�CL���_��;�
���[8��
�E��;nv9�[�b-�~j��5��@�
-�7�>��)�][
`C�/��A�;i�����+9�f���$�2����(P�m8�������h5���8���On�Jf����o�=�	�����������g.��n�7ylPO�.+o(�����K�b�y���y�M)�����Kd1��brh�	i=��&V�|���[:�q����T>e$�9�Nn�c�����%A���t�����=��B#�Z%���~���||d�Pb��W�����S���������?)��&Z�.��-��L2`��)�Gf]�q�������bm��G�w�T�+/}�Uwn\[��UV�e��W����]�e���������%�R$H�A���j=u
�O��2��������s���9'�
�F��Y���3��2���F�i�'�R_�JH�=������#�m�L���C��p-C����{HoZzI�7Yr��^�?�uvs�Z�;�Z��Q�b+������v�|n�S��m�2��2��
�&���{{[6b9���M������������G$
�����X��~�����k���K}���S�K��G�mL%-����J{�������E6�(�	�h��+�e��-��:!@
��-�����Ve������j�uA���0?�@�)q],��!��"�&u(/��#�"���@f�!�0�� _�P�W����+��D!
������S�q�������������7�[S\-�$,�G�����7��Y�F-.jn��jR!�e����1j14Gf`[�(�B��})3���[&I��C���Q�?��[8<(qU#nqD�M�C'�]�p��r�w���#xT�RU]�L�X���V
�i�e�M�Z��[6����
�����Ap�:!n��@�Per)��l��od%D�s�Gf,���O)
��S����������A�k����%N���s����!NX��&E��[)<m]~�3�me8d�����'�
s]5,���o�p��b��e�T#*���*6�$��ia����������ll��V��UP	�\�J�G����C����b��va�0T����F9}����I��L;V�1��%��3��r���d,��I!��BbTv���4EY��z/A�5�v��J��/y+L��}F�z��i%�fR�%b�"���[ua����X��V8��}�(v���0�V�\2�1��_��=��@�6��0;�dF��y ��pUI@�/���sE�����Z[����CM/$��_��� N~�5��,N�9���������T��9[�u��0'My0B/�# G[�"�acB�9fs}����|�

wG���4���$6�����k�E�\��� c:��s@	��V��R�(�Ku�A��~[e	s�s���M��l{Vh:��h�m���hh`�K��c3����!�

����o��
�9G�������{�3Y�������>[S����F������*��H�����yH��&��a������J`)!���%>����=}wP���5�����l��e�v4���`�����x���o.M�lTP�R�o�'��S���Yh��\��G�����Z�hi�y������zrnA����`SV�r���<��s���5���07q�Y��vZ(��/���0�
��;���0W:��8��CL�GL�{��S]�'��c��:�=W9rK�%�!��X��17������"�b����#�g�v}����L^��1B3���Wp�b+i<]�$v�����4T�V]����dK8���X��ui�/pZ~U>��J������.;�������������������������G�S�#iX
#6Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Albe Laurenz (#4)
Re: [v9.3] writable foreign tables

2012/8/27 Albe Laurenz <laurenz.albe@wien.gv.at>:

Kohei KaiGai wrote:

2012/8/25 Robert Haas <robertmhaas@gmail.com>:

On Thu, Aug 23, 2012 at 1:10 AM, Kohei KaiGai <kaigai@kaigai.gr.jp>

wrote:

It is a responsibility of FDW extension (and DBA) to ensure each
foreign-row has a unique identifier that has 48-bits width integer
data type in maximum.

It strikes me as incredibly short-sighted to decide that the row
identifier has to have the same format as what our existing heap AM
happens to have. I think we need to allow the row identifier to be

of

any data type, and even compound. For example, the foreign side

might

have no equivalent of CTID, and thus use primary key. And the

primary

key might consist of an integer and a string, or some such.

I assume it is a task of FDW extension to translate between the pseudo
ctid and the primary key in remote side.

For example, if primary key of the remote table is Text data type, an

idea

is to use a hash table to track the text-formed primary being

associated

with a particular 48-bits integer. The pseudo ctid shall be utilized

to track

the tuple to be modified on the scan-stage, then FDW can reference the
hash table to pull-out the primary key to be provided on the prepared
statement.

And what if there is a hash collision? Then you would not be able to
determine which row is meant.

Even if we had a hash collision, each hash entry can have the original
key itself to be compared. But anyway, I love the idea to support
an opaque pointer to track particular remote-row rather.

I agree with Robert that this should be flexible enough to cater for
all kinds of row identifiers. Oracle, for example, uses ten byte
identifiers which would give me a headache with your suggested design.

Do we have some other reasonable ideas?

Would it be too invasive to introduce a new pointer in TupleTableSlot
that is NULL for anything but virtual tuples from foreign tables?

I'm not certain whether the duration of TupleTableSlot is enough to
carry a private datum between scan and modify stage.
For example, the TupleTableSlot shall be cleared at ExecNestLoop
prior to the slot being delivered to ExecModifyTuple.

postgres=# EXPLAIN UPDATE t1 SET b = 'abcd' WHERE a IN (SELECT x FROM
t2 WHERE x % 2 = 0);
QUERY PLAN
-------------------------------------------------------------------------------
Update on t1 (cost=0.00..54.13 rows=6 width=16)
-> Nested Loop (cost=0.00..54.13 rows=6 width=16)
-> Seq Scan on t2 (cost=0.00..28.45 rows=6 width=10)
Filter: ((x % 2) = 0)
-> Index Scan using t1_pkey on t1 (cost=0.00..4.27 rows=1 width=10)
Index Cond: (a = t2.x)
(6 rows)

Is it possible to utilize ctid field to move a private pointer?
TID data type is internally represented as a pointer to ItemPointerData,
so it has enough width to track an opaque formed remote-row identifier;
including string, int64 or others.

One disadvantage is "ctid" system column shows a nonsense value
when user explicitly references this system column. But it does not
seems to me a fundamental problem, because we didn't give any
special meaning on the "ctid" field of foreign table.

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

#7Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Shigeru HANADA (#5)
Re: [v9.3] writable foreign tables

2012/8/27 Shigeru HANADA <shigeru.hanada@gmail.com>:

Kaigai-san,

On Thu, Aug 23, 2012 at 2:10 PM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

The patched portion at contrib/file_fdw.c does not make sense
actually. It just prints messages for each invocation.
It is just a proof-of-concept to show possibility of implementation
based on real RDBMS.

Attached is a tar ball of pgsql_fdw. It's WIP and contains no
document, but it would be enough for your PoC purpose. Usage and
features are same as the last version posted for 9.2 cycle.
# I'll post finished patch in the CF-Sep.

Thanks, it is helpful to work on.

Here are random comments for your PoC patch:

+ As Robert says, using CTID as virtual tuple identifier doesn't seem
nice when considering various FDWs for NoSQL or RDBMS. Having abstract
layer between FDWs and tuple sounds better, but implementing it by each
FDW seems useless effort. Do yo have any idea of generic mechanism for
tuple mapping?

As I wrote in the previous message, isn't it a reasonable idea to move
a private datum (instead of alternate key) on the "ctid" field which has
been internally represented as a pointer to indicate ItemPointerData?

+ Do you have any plan about deparsing local qualifiers into remote
query to avoid repeated query submission? This would improve
performance of big UPDATE, but its use case might be limited to
statements which consist of one foreign table. For this case, we can
consider pass-through mode as second way.

I think, FDW should run UPDATE or DELETE statement at the scan
stage on remote-side, then return a pseudo result to scanner, in case
of the statement is "enough simple", like no qualifier, no returning, etc...
The callback on ExecUpdate/ExecDelete will perform just a stub; that
does not actually work except for increment of affected rows.

+ I have not read your patch closely yet, but I wonder how we can know
which column is actually updated. If we have only updated image of
tuple, we have to update all remote columns by "new" values?

It seems to me TargetEntry of the parse tree can inform us which column
should be modified on UPDATE or INSERT. If it has just a Var element
that reference original table as-is, it means here is no change.

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

#8Albe Laurenz
laurenz.albe@wien.gv.at
In reply to: Kohei KaiGai (#6)
Re: [v9.3] writable foreign tables

Kohei KaiGai wrote:

It is a responsibility of FDW extension (and DBA) to ensure each
foreign-row has a unique identifier that has 48-bits width integer
data type in maximum.

For example, if primary key of the remote table is Text data type,
an idea is to use a hash table to track the text-formed primary
being associated with a particular 48-bits integer.

Even if we had a hash collision, each hash entry can have the original
key itself to be compared. But anyway, I love the idea to support
an opaque pointer to track particular remote-row rather.

Me too.

Do we have some other reasonable ideas?

I'm not certain whether the duration of TupleTableSlot is enough to
carry a private datum between scan and modify stage.

Is it possible to utilize ctid field to move a private pointer?
TID data type is internally represented as a pointer to

ItemPointerData,

so it has enough width to track an opaque formed remote-row

identifier;

including string, int64 or others.

One disadvantage is "ctid" system column shows a nonsense value
when user explicitly references this system column. But it does not
seems to me a fundamental problem, because we didn't give any
special meaning on the "ctid" field of foreign table.

I can't say if (ab)using the field that way would cause other
problems, but I don't think that "nonsense values" are a problem.
The pointer would stay the same for the duration of the foreign
scan, which I think is as good a ctid for a foreign table as
anybody should reasonably ask.

BTW, I see the following comment in htup.h:

* t_self and t_tableOid should be valid if the HeapTupleData points to
* a disk buffer, or if it represents a copy of a tuple on disk. They
* should be explicitly set invalid in manufactured tuples.

I don't know if "invalid" means "zero" in that case.

Yours,
Laurenz Albe

#9Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Albe Laurenz (#8)
Re: [v9.3] writable foreign tables

2012/8/28 Albe Laurenz <laurenz.albe@wien.gv.at>:

Kohei KaiGai wrote:

It is a responsibility of FDW extension (and DBA) to ensure each
foreign-row has a unique identifier that has 48-bits width integer
data type in maximum.

For example, if primary key of the remote table is Text data type,
an idea is to use a hash table to track the text-formed primary
being associated with a particular 48-bits integer.

Even if we had a hash collision, each hash entry can have the original
key itself to be compared. But anyway, I love the idea to support
an opaque pointer to track particular remote-row rather.

Me too.

Do we have some other reasonable ideas?

I'm not certain whether the duration of TupleTableSlot is enough to
carry a private datum between scan and modify stage.

Is it possible to utilize ctid field to move a private pointer?
TID data type is internally represented as a pointer to

ItemPointerData,

so it has enough width to track an opaque formed remote-row

identifier;

including string, int64 or others.

One disadvantage is "ctid" system column shows a nonsense value
when user explicitly references this system column. But it does not
seems to me a fundamental problem, because we didn't give any
special meaning on the "ctid" field of foreign table.

I can't say if (ab)using the field that way would cause other
problems, but I don't think that "nonsense values" are a problem.
The pointer would stay the same for the duration of the foreign
scan, which I think is as good a ctid for a foreign table as
anybody should reasonably ask.

BTW, I see the following comment in htup.h:

* t_self and t_tableOid should be valid if the HeapTupleData points to
* a disk buffer, or if it represents a copy of a tuple on disk. They
* should be explicitly set invalid in manufactured tuples.

I don't know if "invalid" means "zero" in that case.

ItemPointerSetInvalid is declared as follows:

/*
* ItemPointerSetInvalid
* Sets a disk item pointer to be invalid.
*/
#define ItemPointerSetInvalid(pointer) \
( \
AssertMacro(PointerIsValid(pointer)), \
BlockIdSet(&((pointer)->ip_blkid), InvalidBlockNumber), \
(pointer)->ip_posid = InvalidOffsetNumber \
)

Since ItemPointerGetBlockNumber() and ItemPointerGetOffsetNumber()
checks whether the given ItemPointer is valid, FDWs may have to put
a dummy ItemPointerData on head of their private datum to avoid
the first 6-bytes having zero.

For example, the following data structure is safe to carry an opaque
datum without false-positive of invalid ctid.

typedef struct {
ItemPointerData dumm
char *pk_of_remote_table;
} my_pseudo_rowid;

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

#10Tom Lane
tgl@sss.pgh.pa.us
In reply to: Kohei KaiGai (#6)
Re: [v9.3] writable foreign tables

Kohei KaiGai <kaigai@kaigai.gr.jp> writes:

Would it be too invasive to introduce a new pointer in TupleTableSlot
that is NULL for anything but virtual tuples from foreign tables?

I'm not certain whether the duration of TupleTableSlot is enough to
carry a private datum between scan and modify stage.

It's not.

Is it possible to utilize ctid field to move a private pointer?

UPDATEs and DELETEs do not rely on the ctid field of tuples to carry the
TID from scan to modify --- in fact, most of the time what the modify
step is going to get is a "virtual" TupleTableSlot that hasn't even
*got* a physical CTID field.

Instead, the planner arranges for the TID to be carried up as an
explicit resjunk column named ctid. (Currently this is done in
rewriteTargetListUD(), but see also preptlist.c which does some related
things for SELECT FOR UPDATE.)

I'm inclined to think that what we need here is for FDWs to be able to
modify the details of that behavior, at least to the extent of being
able to specify a different data type than TID for the row
identification column.

regards, tom lane

#11Tom Lane
tgl@sss.pgh.pa.us
In reply to: Kohei KaiGai (#7)
Re: [v9.3] writable foreign tables

Kohei KaiGai <kaigai@kaigai.gr.jp> writes:

It seems to me TargetEntry of the parse tree can inform us which column
should be modified on UPDATE or INSERT. If it has just a Var element
that reference original table as-is, it means here is no change.

Only if you're not going to support BEFORE triggers modifying the row...

regards, tom lane

#12David Fetter
david@fetter.org
In reply to: Tom Lane (#11)
Re: [v9.3] writable foreign tables

On Tue, Aug 28, 2012 at 10:58:25AM -0400, Tom Lane wrote:

Kohei KaiGai <kaigai@kaigai.gr.jp> writes:

It seems to me TargetEntry of the parse tree can inform us which column
should be modified on UPDATE or INSERT. If it has just a Var element
that reference original table as-is, it means here is no change.

Only if you're not going to support BEFORE triggers modifying the row...

+1 for supporting these.

Speaking of triggers on foreign tables, what's needed to support them
independent of support at the FDW level for writing on foreign tables,
or does that even make sense?

Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

#13Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: David Fetter (#12)
Re: [v9.3] writable foreign tables

2012/8/28 David Fetter <david@fetter.org>:

On Tue, Aug 28, 2012 at 10:58:25AM -0400, Tom Lane wrote:

Kohei KaiGai <kaigai@kaigai.gr.jp> writes:

It seems to me TargetEntry of the parse tree can inform us which column
should be modified on UPDATE or INSERT. If it has just a Var element
that reference original table as-is, it means here is no change.

Only if you're not going to support BEFORE triggers modifying the row...

+1 for supporting these.

Speaking of triggers on foreign tables, what's needed to support them
independent of support at the FDW level for writing on foreign tables,
or does that even make sense?

I agree with trigger support on foreign tables is definitely useful feature,
even though it does not have capability to replace the writable foreign
table functionality.

In case when foreign-table definition does not contain a column mapped
with primary-key column in remote-side, the trigger function cannot
determine which row should be updated / deleted.
It is a situation that FDW driver should track a particular remote-row using
its identifier.

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

#14David Fetter
david@fetter.org
In reply to: Kohei KaiGai (#13)
Re: [v9.3] writable foreign tables

On Tue, Aug 28, 2012 at 05:18:34PM +0200, Kohei KaiGai wrote:

2012/8/28 David Fetter <david@fetter.org>:

On Tue, Aug 28, 2012 at 10:58:25AM -0400, Tom Lane wrote:

Kohei KaiGai <kaigai@kaigai.gr.jp> writes:

It seems to me TargetEntry of the parse tree can inform us
which column should be modified on UPDATE or INSERT. If it has
just a Var element that reference original table as-is, it
means here is no change.

Only if you're not going to support BEFORE triggers modifying the
row...

+1 for supporting these.

Speaking of triggers on foreign tables, what's needed to support
them independent of support at the FDW level for writing on
foreign tables, or does that even make sense?

I agree with trigger support on foreign tables is definitely useful
feature, even though it does not have capability to replace the
writable foreign table functionality.

With utmost respect, trigger support does make it possible to write to
foreign tables using a whole-row comparison with the effect that all
whole-row matches would be affected. This is how DBI-Link does it
currently.

In case when foreign-table definition does not contain a column
mapped with primary-key column in remote-side, the trigger function
cannot determine which row should be updated / deleted. It is a
situation that FDW driver should track a particular remote-row using
its identifier.

Generated identifiers and whole-row matching are two ways to approach
this. There are likely others, especially in cases where people have
special knowledge of the remote source.

Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

#15Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Tom Lane (#10)
Re: [v9.3] writable foreign tables

2012/8/28 Tom Lane <tgl@sss.pgh.pa.us>:

Kohei KaiGai <kaigai@kaigai.gr.jp> writes:

Would it be too invasive to introduce a new pointer in TupleTableSlot
that is NULL for anything but virtual tuples from foreign tables?

I'm not certain whether the duration of TupleTableSlot is enough to
carry a private datum between scan and modify stage.

It's not.

Is it possible to utilize ctid field to move a private pointer?

UPDATEs and DELETEs do not rely on the ctid field of tuples to carry the
TID from scan to modify --- in fact, most of the time what the modify
step is going to get is a "virtual" TupleTableSlot that hasn't even
*got* a physical CTID field.

Instead, the planner arranges for the TID to be carried up as an
explicit resjunk column named ctid. (Currently this is done in
rewriteTargetListUD(), but see also preptlist.c which does some related
things for SELECT FOR UPDATE.)

I'm inclined to think that what we need here is for FDWs to be able to
modify the details of that behavior, at least to the extent of being
able to specify a different data type than TID for the row
identification column.

Hmm. It seems to me a straight-forward solution rather than ab-use
of ctid system column. Probably, cstring data type is more suitable
to carry a private datum between scan and modify stage.

One problem I noticed is how FDW driver returns an extra field that
is in neither system nor regular column.
Number of columns and its data type are defined with TupleDesc of
the target foreign-table, so we also need a feature to extend it on
run-time. For example, FDW driver may have to be able to extend
a "virtual" column with cstring data type, even though the target
foreign table does not have such a column.

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

#16Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: David Fetter (#14)
Re: [v9.3] writable foreign tables

2012/8/28 David Fetter <david@fetter.org>:

On Tue, Aug 28, 2012 at 05:18:34PM +0200, Kohei KaiGai wrote:

2012/8/28 David Fetter <david@fetter.org>:

On Tue, Aug 28, 2012 at 10:58:25AM -0400, Tom Lane wrote:

Kohei KaiGai <kaigai@kaigai.gr.jp> writes:

It seems to me TargetEntry of the parse tree can inform us
which column should be modified on UPDATE or INSERT. If it has
just a Var element that reference original table as-is, it
means here is no change.

Only if you're not going to support BEFORE triggers modifying the
row...

+1 for supporting these.

Speaking of triggers on foreign tables, what's needed to support
them independent of support at the FDW level for writing on
foreign tables, or does that even make sense?

I agree with trigger support on foreign tables is definitely useful
feature, even though it does not have capability to replace the
writable foreign table functionality.

With utmost respect, trigger support does make it possible to write to
foreign tables using a whole-row comparison with the effect that all
whole-row matches would be affected. This is how DBI-Link does it
currently.

In case when foreign-table definition does not contain a column
mapped with primary-key column in remote-side, the trigger function
cannot determine which row should be updated / deleted. It is a
situation that FDW driver should track a particular remote-row using
its identifier.

Generated identifiers and whole-row matching are two ways to approach
this. There are likely others, especially in cases where people have
special knowledge of the remote source.

One major problem is how to carry the generated identifiers on run-time,
even though we have no slot except for system and regular columns
defined in TupleDesc of the target foreign tables.
It may need a feature to expand TupleDesc on demand.

Of course, I don't deny the benefit of trigger support on foreign-tables.
Both writable-feature and trigger-support can be supported simultaneously.

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

#17David Fetter
david@fetter.org
In reply to: Kohei KaiGai (#16)
Re: [v9.3] writable foreign tables

On Tue, Aug 28, 2012 at 06:08:59PM +0200, Kohei KaiGai wrote:

2012/8/28 David Fetter <david@fetter.org>:

On Tue, Aug 28, 2012 at 05:18:34PM +0200, Kohei KaiGai wrote:

2012/8/28 David Fetter <david@fetter.org>:

On Tue, Aug 28, 2012 at 10:58:25AM -0400, Tom Lane wrote:

Kohei KaiGai <kaigai@kaigai.gr.jp> writes:

It seems to me TargetEntry of the parse tree can inform us
which column should be modified on UPDATE or INSERT. If it has
just a Var element that reference original table as-is, it
means here is no change.

Only if you're not going to support BEFORE triggers modifying the
row...

+1 for supporting these.

Generated identifiers and whole-row matching are two ways to approach
this. There are likely others, especially in cases where people have
special knowledge of the remote source.

One major problem is how to carry the generated identifiers on run-time,
even though we have no slot except for system and regular columns
defined in TupleDesc of the target foreign tables.
It may need a feature to expand TupleDesc on demand.

Could be. You know a lot more about the implementation details than I do.

Of course, I don't deny the benefit of trigger support on foreign-tables.
Both writable-feature and trigger-support can be supported simultaneously.

Do you see these as independent features, or is there some essential
overlap?

Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics

Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate

#18Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Kohei KaiGai (#15)
Re: [v9.3] writable foreign tables

2012/8/28 Kohei KaiGai <kaigai@kaigai.gr.jp>:

2012/8/28 Tom Lane <tgl@sss.pgh.pa.us>:

Kohei KaiGai <kaigai@kaigai.gr.jp> writes:

Would it be too invasive to introduce a new pointer in TupleTableSlot
that is NULL for anything but virtual tuples from foreign tables?

I'm not certain whether the duration of TupleTableSlot is enough to
carry a private datum between scan and modify stage.

It's not.

Is it possible to utilize ctid field to move a private pointer?

UPDATEs and DELETEs do not rely on the ctid field of tuples to carry the
TID from scan to modify --- in fact, most of the time what the modify
step is going to get is a "virtual" TupleTableSlot that hasn't even
*got* a physical CTID field.

Instead, the planner arranges for the TID to be carried up as an
explicit resjunk column named ctid. (Currently this is done in
rewriteTargetListUD(), but see also preptlist.c which does some related
things for SELECT FOR UPDATE.)

I'm inclined to think that what we need here is for FDWs to be able to
modify the details of that behavior, at least to the extent of being
able to specify a different data type than TID for the row
identification column.

Hmm. It seems to me a straight-forward solution rather than ab-use
of ctid system column. Probably, cstring data type is more suitable
to carry a private datum between scan and modify stage.

One problem I noticed is how FDW driver returns an extra field that
is in neither system nor regular column.
Number of columns and its data type are defined with TupleDesc of
the target foreign-table, so we also need a feature to extend it on
run-time. For example, FDW driver may have to be able to extend
a "virtual" column with cstring data type, even though the target
foreign table does not have such a column.

I tried to investigate the related routines.

TupleDesc of TupleTableSlot associated with ForeignScanState
is initialized at ExecInitForeignScan as literal.
ExecAssignScanType assigns TupleDesc of the target foreign-
table on tts_tupleDescriptor, "as-is".
It is the reason why IterateForeignScan cannot return a private
datum except for the columns being declared as regular ones.

Confrontation between ForeignScan and SubqueryScan tell us
the point to be extended. It assigns TupleDesc of the subplan
generated at run-time.
It seems to me ForeignScan will be able to adopt similar idea;
that allows to append "pseudo-column" onto TupleDesc to
carry identifier of remote-rows to be updated / deleted, if
FDW driver can control TupleDesc being set, instead of the
one come from relation's definition as-is.

Any comment please. Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

#19Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: David Fetter (#17)
Re: [v9.3] writable foreign tables

2012/8/28 David Fetter <david@fetter.org>:

On Tue, Aug 28, 2012 at 06:08:59PM +0200, Kohei KaiGai wrote:

2012/8/28 David Fetter <david@fetter.org>:

On Tue, Aug 28, 2012 at 05:18:34PM +0200, Kohei KaiGai wrote:

2012/8/28 David Fetter <david@fetter.org>:

On Tue, Aug 28, 2012 at 10:58:25AM -0400, Tom Lane wrote:

Kohei KaiGai <kaigai@kaigai.gr.jp> writes:

It seems to me TargetEntry of the parse tree can inform us
which column should be modified on UPDATE or INSERT. If it has
just a Var element that reference original table as-is, it
means here is no change.

Only if you're not going to support BEFORE triggers modifying the
row...

+1 for supporting these.

Generated identifiers and whole-row matching are two ways to approach
this. There are likely others, especially in cases where people have
special knowledge of the remote source.

One major problem is how to carry the generated identifiers on run-time,
even though we have no slot except for system and regular columns
defined in TupleDesc of the target foreign tables.
It may need a feature to expand TupleDesc on demand.

Could be. You know a lot more about the implementation details than I do.

Of course, I don't deny the benefit of trigger support on foreign-tables.
Both writable-feature and trigger-support can be supported simultaneously.

Do you see these as independent features, or is there some essential
overlap?

If we stand on the viewpoint that foreign-tables should perform as if regular
tables, I don't think its writer feature should depend on trigger stuff.
They can work independently.

On the other hand, trigger feature gives users flexibility to control the data
to be written, as if regular tables. We shouldn't miss the point.
At least, I don't think we have some technical differences to support row-level
triggers on foreign tables.

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

#20Albe Laurenz
laurenz.albe@wien.gv.at
In reply to: Tom Lane (#10)
Re: [v9.3] writable foreign tables

Tom Lane wrote:

Kohei KaiGai <kaigai@kaigai.gr.jp> writes:

Laurenz Albe wrote:

Would it be too invasive to introduce a new pointer in

TupleTableSlot

that is NULL for anything but virtual tuples from foreign tables?

I'm not certain whether the duration of TupleTableSlot is enough to
carry a private datum between scan and modify stage.

It's not.

Is it possible to utilize ctid field to move a private pointer?

UPDATEs and DELETEs do not rely on the ctid field of tuples to carry

the

TID from scan to modify --- in fact, most of the time what the modify
step is going to get is a "virtual" TupleTableSlot that hasn't even
*got* a physical CTID field.

Instead, the planner arranges for the TID to be carried up as an
explicit resjunk column named ctid. (Currently this is done in
rewriteTargetListUD(), but see also preptlist.c which does some

related

things for SELECT FOR UPDATE.)

I'm inclined to think that what we need here is for FDWs to be able to
modify the details of that behavior, at least to the extent of being
able to specify a different data type than TID for the row
identification column.

Would that imply inventing a new system attribute for
"foreign tid"?

Yours,
Laurenz Albe

#21Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Albe Laurenz (#20)
Re: [v9.3] writable foreign tables

2012/9/13 Albe Laurenz <laurenz.albe@wien.gv.at>:

Tom Lane wrote:

Kohei KaiGai <kaigai@kaigai.gr.jp> writes:

Laurenz Albe wrote:

Would it be too invasive to introduce a new pointer in

TupleTableSlot

that is NULL for anything but virtual tuples from foreign tables?

I'm not certain whether the duration of TupleTableSlot is enough to
carry a private datum between scan and modify stage.

It's not.

Is it possible to utilize ctid field to move a private pointer?

UPDATEs and DELETEs do not rely on the ctid field of tuples to carry

the

TID from scan to modify --- in fact, most of the time what the modify
step is going to get is a "virtual" TupleTableSlot that hasn't even
*got* a physical CTID field.

Instead, the planner arranges for the TID to be carried up as an
explicit resjunk column named ctid. (Currently this is done in
rewriteTargetListUD(), but see also preptlist.c which does some

related

things for SELECT FOR UPDATE.)

I'm inclined to think that what we need here is for FDWs to be able to
modify the details of that behavior, at least to the extent of being
able to specify a different data type than TID for the row
identification column.

Would that imply inventing a new system attribute for
"foreign tid"?

It is an idea to implement this feature with minimum code side.

However, my preference is to support "pseudo-column" approach
rather than system columns, because it also can be utilized for
another interesting feature that enables to push-down target entry
onto remote side.
So, I'd like to try to support a feature that allows foreign-table to
return "pseudo-column" in addition to its table definition to move
row-id of remote tuples, as primary purpose of this.

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

#22Tom Lane
tgl@sss.pgh.pa.us
In reply to: Albe Laurenz (#20)
Re: [v9.3] writable foreign tables

"Albe Laurenz" <laurenz.albe@wien.gv.at> writes:

Tom Lane wrote:

Instead, the planner arranges for the TID to be carried up as an
explicit resjunk column named ctid. (Currently this is done in
rewriteTargetListUD(), but see also preptlist.c which does some
related things for SELECT FOR UPDATE.)

I'm inclined to think that what we need here is for FDWs to be able to
modify the details of that behavior, at least to the extent of being
able to specify a different data type than TID for the row
identification column.

Would that imply inventing a new system attribute for
"foreign tid"?

No, I think you missed the point of what I wrote completely. The target
row ID is not treated as a system attribute during UPDATE/DELETE. It's
an ordinary data column that's silently added to what the user wrote.

regards, tom lane

#23Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Kohei KaiGai (#18)
1 attachment(s)
Re: [v9.3] writable foreign tables

2012/8/29 Kohei KaiGai <kaigai@kaigai.gr.jp>:

2012/8/28 Kohei KaiGai <kaigai@kaigai.gr.jp>:

2012/8/28 Tom Lane <tgl@sss.pgh.pa.us>:

Kohei KaiGai <kaigai@kaigai.gr.jp> writes:

Would it be too invasive to introduce a new pointer in TupleTableSlot
that is NULL for anything but virtual tuples from foreign tables?

I'm not certain whether the duration of TupleTableSlot is enough to
carry a private datum between scan and modify stage.

It's not.

Is it possible to utilize ctid field to move a private pointer?

UPDATEs and DELETEs do not rely on the ctid field of tuples to carry the
TID from scan to modify --- in fact, most of the time what the modify
step is going to get is a "virtual" TupleTableSlot that hasn't even
*got* a physical CTID field.

Instead, the planner arranges for the TID to be carried up as an
explicit resjunk column named ctid. (Currently this is done in
rewriteTargetListUD(), but see also preptlist.c which does some related
things for SELECT FOR UPDATE.)

I'm inclined to think that what we need here is for FDWs to be able to
modify the details of that behavior, at least to the extent of being
able to specify a different data type than TID for the row
identification column.

Hmm. It seems to me a straight-forward solution rather than ab-use
of ctid system column. Probably, cstring data type is more suitable
to carry a private datum between scan and modify stage.

One problem I noticed is how FDW driver returns an extra field that
is in neither system nor regular column.
Number of columns and its data type are defined with TupleDesc of
the target foreign-table, so we also need a feature to extend it on
run-time. For example, FDW driver may have to be able to extend
a "virtual" column with cstring data type, even though the target
foreign table does not have such a column.

I tried to investigate the related routines.

TupleDesc of TupleTableSlot associated with ForeignScanState
is initialized at ExecInitForeignScan as literal.
ExecAssignScanType assigns TupleDesc of the target foreign-
table on tts_tupleDescriptor, "as-is".
It is the reason why IterateForeignScan cannot return a private
datum except for the columns being declared as regular ones.

The attached patch improved its design according to the upthread
discussion. It now got away from ab-use of "ctid" field, and adopts
a concept of pseudo-column to hold row-id with opaque data type
instead.

Pseudo-column is Var reference towards attribute-number larger
than number of attributes on the target relation; thus, it is not
a substantial object. It is normally unavailable to reference such
a larger attribute number because TupleDesc of each ScanState
associated with a particular relation is initialized at ExecInitNode.

The patched ExecInitForeignScan was extended to generate its
own TupleDesc including pseudo-column definitions on the fly,
instead of relation's one, when scan-plan of foreign-table requires
to have pseudo-columns.

Right now, the only possible pseudo-column is "rowid" being
injected at rewriteTargetListUD(). It has no data format
restriction like "ctid" because of VOID data type.
FDW extension can set an appropriate value on the "rowid"
field in addition to contents of regular columns at
IterateForeignScan method, to track which remote row should
be updated or deleted.

Another possible usage of this pseudo-column is push-down
of target-list including complex calculation. It may enable to
move complex mathematical formula into remote devices
(such as GPU device?) instead of just a reference of Var node.

This patch adds a new interface: GetForeignRelInfo being invoked
from get_relation_info() to adjust width of RelOptInfo->attr_needed
according to the target-list which may contain "rowid" pseudo-column.
Some FDW extension may use this interface to push-down a part of
target list into remote side, even though I didn't implement this
feature on file_fdw.

RelOptInfo->max_attr is a good marker whether the plan shall have
pseudo-column reference. Then, ExecInitForeignScan determines
whether it should generate a TupleDesc, or not.

The "rowid" is fetched using ExecGetJunkAttribute as we are currently
doing on regular tables using "ctid", then it shall be delivered to
ExecUpdate or ExecDelete. We can never expect the fist argument of
them now, so "ItemPointer tupleid" redefined to "Datum rowid", and
argument of BR-trigger routines redefined also.

[kaigai@iwashi sepgsql]$ cat ~/testfile.csv
10 aaa
11 bbb
12 ccc
13 ddd
14 eee
15 fff
[kaigai@iwashi sepgsql]$ psql postgres
psql (9.3devel)
Type "help" for help.

postgres=# UPDATE ftbl SET b = md5(b) WHERE a > 12 RETURNING *;
INFO: ftbl is the target relation of UPDATE
INFO: fdw_file: BeginForeignModify method
INFO: fdw_file: UPDATE (lineno = 4)
INFO: fdw_file: UPDATE (lineno = 5)
INFO: fdw_file: UPDATE (lineno = 6)
INFO: fdw_file: EndForeignModify method
a | b
----+----------------------------------
13 | 77963b7a931377ad4ab5ad6a9cd718aa
14 | d2f2297d6e829cd3493aa7de4416a18f
15 | 343d9040a671c45832ee5381860e2996
(3 rows)

UPDATE 3
postgres=# DELETE FROM ftbl WHERE a % 2 = 1 RETURNING *;
INFO: ftbl is the target relation of DELETE
INFO: fdw_file: BeginForeignModify method
INFO: fdw_file: DELETE (lineno = 2)
INFO: fdw_file: DELETE (lineno = 4)
INFO: fdw_file: DELETE (lineno = 6)
INFO: fdw_file: EndForeignModify method
a | b
----+-----
11 | bbb
13 | ddd
15 | fff
(3 rows)

DELETE 3

In addition, there is a small improvement. ExecForeignInsert,
ExecForeignUpdate and ExecForeignDelete get being able
to return number of processed rows; that allows to push-down
whole the statement into remote-side, if it is enough simple
(e.g, delete statement without any condition).

Even though it does not make matter right now, pseudo-columns
should be adjusted when foreign-table is referenced with table
inheritance feature, because an attribute number being enough
large in parent table is not enough large in child table.
We need to fix up them until foreign table feature got inheritance
capability.

I didn't update the documentation stuff because I positioned
the state of this patch as proof-of-concept now. Please note that.

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

Attachments:

pgsql-v9.3-writable-fdw-poc.v2.patchapplication/octet-stream; name=pgsql-v9.3-writable-fdw-poc.v2.patchDownload
 contrib/file_fdw/file_fdw.c             | 176 +++++++++++++++++++++++++++++++-
 src/backend/commands/trigger.c          |  12 ++-
 src/backend/executor/execMain.c         |  34 +++++-
 src/backend/executor/nodeForeignscan.c  | 136 +++++++++++++++++++++++-
 src/backend/executor/nodeModifyTable.c  | 142 +++++++++++++++++++++-----
 src/backend/nodes/copyfuncs.c           |   1 +
 src/backend/nodes/outfuncs.c            |   1 +
 src/backend/optimizer/plan/createplan.c |  19 ++++
 src/backend/optimizer/plan/initsplan.c  |  10 +-
 src/backend/optimizer/plan/planmain.c   |   2 +-
 src/backend/optimizer/plan/planner.c    |   2 +-
 src/backend/optimizer/prep/prepunion.c  |   3 +-
 src/backend/optimizer/util/plancat.c    |  23 ++++-
 src/backend/optimizer/util/relnode.c    |   7 +-
 src/backend/rewrite/rewriteHandler.c    |  55 ++++++++--
 src/include/commands/trigger.h          |   8 +-
 src/include/foreign/fdwapi.h            |  25 +++++
 src/include/nodes/execnodes.h           |  13 ++-
 src/include/nodes/plannodes.h           |   1 +
 src/include/optimizer/pathnode.h        |   2 +-
 src/include/optimizer/plancat.h         |   2 +-
 src/include/optimizer/planmain.h        |   3 +-
 22 files changed, 608 insertions(+), 69 deletions(-)

diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
index 81fc4e2..7d9bd44 100644
--- a/contrib/file_fdw/file_fdw.c
+++ b/contrib/file_fdw/file_fdw.c
@@ -34,6 +34,7 @@
 #include "optimizer/var.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
+#include "utils/lsyscache.h"
 
 PG_MODULE_MAGIC;
 
@@ -87,6 +88,7 @@ typedef struct FileFdwPlanState
 	List	   *options;		/* merged COPY options, excluding filename */
 	BlockNumber pages;			/* estimate of file's physical size */
 	double		ntuples;		/* estimate of number of rows in file */
+	DefElem	   *anum_rowid;		/* attribute number of rowid */
 } FileFdwPlanState;
 
 /*
@@ -97,6 +99,8 @@ typedef struct FileFdwExecutionState
 	char	   *filename;		/* file to read */
 	List	   *options;		/* merged COPY options, excluding filename */
 	CopyState	cstate;			/* state of reading file */
+	int			lineno;			/* pseudo ctid of the file */
+	AttrNumber	anum_rowid;		/* attribute number of rowid */
 } FileFdwExecutionState;
 
 /*
@@ -111,6 +115,10 @@ PG_FUNCTION_INFO_V1(file_fdw_validator);
 /*
  * FDW callback routines
  */
+static void fileGetForeignRelInfo(PlannerInfo *root,
+								  RelOptInfo *baserel,
+								  Oid foreigntableid,
+								  bool inhparent, List *targetList);
 static void fileGetForeignRelSize(PlannerInfo *root,
 					  RelOptInfo *baserel,
 					  Oid foreigntableid);
@@ -131,6 +139,19 @@ static void fileEndForeignScan(ForeignScanState *node);
 static bool fileAnalyzeForeignTable(Relation relation,
 						AcquireSampleRowsFunc *func,
 						BlockNumber *totalpages);
+static void fileBeginForeignModify(CmdType operation,
+								   ResultRelInfo *resultRelInfo,
+								   EState *estate,
+								   Plan *subplan,
+								   int eflags);
+static int	fileExecForeignInsert(ResultRelInfo *resultRelInfo,
+								  HeapTuple tuple);
+static int	fileExecForeignDelete(ResultRelInfo *resultRelInfo,
+								  Datum rowid);
+static int	fileExecForeignUpdate(ResultRelInfo *resultRelInfo,
+								  Datum rowid,
+								  HeapTuple tuple);
+static void fileEndForeignModify(ResultRelInfo *resultRelInfo);
 
 /*
  * Helper functions
@@ -169,7 +190,13 @@ file_fdw_handler(PG_FUNCTION_ARGS)
 	fdwroutine->IterateForeignScan = fileIterateForeignScan;
 	fdwroutine->ReScanForeignScan = fileReScanForeignScan;
 	fdwroutine->EndForeignScan = fileEndForeignScan;
+	fdwroutine->GetForeignRelInfo = fileGetForeignRelInfo;
 	fdwroutine->AnalyzeForeignTable = fileAnalyzeForeignTable;
+	fdwroutine->BeginForeignModify = fileBeginForeignModify;
+	fdwroutine->ExecForeignInsert = fileExecForeignInsert;
+	fdwroutine->ExecForeignDelete = fileExecForeignDelete;
+	fdwroutine->ExecForeignUpdate = fileExecForeignUpdate;
+	fdwroutine->EndForeignModify = fileEndForeignModify;
 
 	PG_RETURN_POINTER(fdwroutine);
 }
@@ -424,6 +451,46 @@ get_file_fdw_attribute_options(Oid relid)
 }
 
 /*
+ * fileGetForeignRelInfo
+ *		Adjust size of baserel->attr_needed according to var references
+ *		within targetList.
+ */
+static void
+fileGetForeignRelInfo(PlannerInfo *root,
+                      RelOptInfo *baserel,
+                      Oid foreigntableid,
+					  bool inhparent, List *targetList)
+{
+	FileFdwPlanState *fdw_private;
+	ListCell   *cell;
+
+	fdw_private = (FileFdwPlanState *) palloc0(sizeof(FileFdwPlanState));
+
+	foreach(cell, targetList)
+	{
+		TargetEntry	*tle = lfirst(cell);
+
+		if (tle->resjunk && strcmp(tle->resname, "rowid")==0)
+		{
+			Bitmapset  *temp = NULL;
+			AttrNumber	anum_rowid;
+			DefElem	   *defel;
+
+			pull_varattnos((Node *)tle, baserel->relid, &temp);
+			anum_rowid = bms_singleton_member(temp)
+				+ FirstLowInvalidHeapAttributeNumber;
+			/* adjust attr_needed of baserel */
+			if (anum_rowid > baserel->max_attr)
+				baserel->max_attr = anum_rowid;
+			defel = makeDefElem("anum_rowid",
+								(Node *)makeInteger(anum_rowid));
+			fdw_private->anum_rowid = defel;
+		}
+	}
+	baserel->fdw_private = fdw_private;
+}
+
+/*
  * fileGetForeignRelSize
  *		Obtain relation size estimates for a foreign table
  */
@@ -432,16 +499,14 @@ fileGetForeignRelSize(PlannerInfo *root,
 					  RelOptInfo *baserel,
 					  Oid foreigntableid)
 {
-	FileFdwPlanState *fdw_private;
+	FileFdwPlanState *fdw_private = (FileFdwPlanState *) baserel->fdw_private;
 
 	/*
 	 * Fetch options.  We only need filename at this point, but we might as
 	 * well get everything and not need to re-fetch it later in planning.
 	 */
-	fdw_private = (FileFdwPlanState *) palloc(sizeof(FileFdwPlanState));
 	fileGetOptions(foreigntableid,
 				   &fdw_private->filename, &fdw_private->options);
-	baserel->fdw_private = (void *) fdw_private;
 
 	/* Estimate relation size */
 	estimate_size(root, baserel, fdw_private);
@@ -473,6 +538,10 @@ fileGetForeignPaths(PlannerInfo *root,
 		coptions = list_make1(makeDefElem("convert_selectively",
 										  (Node *) columns));
 
+	/* save attribute number of rowid pseudo column */
+	if (fdw_private->anum_rowid)
+		coptions = lappend(coptions, fdw_private->anum_rowid);
+
 	/* Estimate costs */
 	estimate_costs(root, baserel, fdw_private,
 				   &startup_cost, &total_cost);
@@ -513,6 +582,35 @@ fileGetForeignPlan(PlannerInfo *root,
 	Index		scan_relid = baserel->relid;
 
 	/*
+	 * XXX - An evidence FDW module can know what kind of accesses are
+	 * requires on the target relation, if UPDATE, INSERT or DELETE.
+	 * It shall be also utilized to appropriate lock level on FDW
+	 * extensions that performs behalf on real RDBMS.
+	 */
+	if (root->parse->resultRelation == baserel->relid)
+	{
+		switch (root->parse->commandType)
+		{
+			case CMD_INSERT:
+				elog(INFO, "%s is the target relation of INSERT",
+					 get_rel_name(foreigntableid));
+				break;
+			case CMD_UPDATE:
+				elog(INFO, "%s is the target relation of UPDATE",
+					 get_rel_name(foreigntableid));
+				break;
+			case CMD_DELETE:
+				elog(INFO, "%s is the target relation of DELETE",
+                     get_rel_name(foreigntableid));
+				break;
+			default:
+				elog(INFO, "%s is the target relation of ??????",
+                     get_rel_name(foreigntableid));
+				break;
+		}
+	}
+
+	/*
 	 * We have no native ability to evaluate restriction clauses, so we just
 	 * put all the scan_clauses into the plan node's qual list for the
 	 * executor to check.  So all we have to do here is strip RestrictInfo
@@ -566,7 +664,10 @@ fileBeginForeignScan(ForeignScanState *node, int eflags)
 	ForeignScan *plan = (ForeignScan *) node->ss.ps.plan;
 	char	   *filename;
 	List	   *options;
+	ListCell   *cell;
+	ListCell   *prev;
 	CopyState	cstate;
+	AttrNumber	anum_rowid = InvalidAttrNumber;
 	FileFdwExecutionState *festate;
 
 	/*
@@ -582,6 +683,21 @@ fileBeginForeignScan(ForeignScanState *node, int eflags)
 	/* Add any options from the plan (currently only convert_selectively) */
 	options = list_concat(options, plan->fdw_private);
 
+	/* Fetch anum_rowid, if exist in options */
+	prev = NULL;
+	foreach (cell, options)
+	{
+		DefElem	   *defel = lfirst(cell);
+
+		if (strcmp(defel->defname, "anum_rowid") == 0)
+		{
+			anum_rowid = intVal(defel->arg);
+			options = list_delete_cell(options, cell, prev);
+			break;
+		}
+		prev = cell;
+	}
+
 	/*
 	 * Create CopyState from FDW options.  We always acquire all columns, so
 	 * as to match the expected ScanTupleSlot signature.
@@ -599,6 +715,8 @@ fileBeginForeignScan(ForeignScanState *node, int eflags)
 	festate->filename = filename;
 	festate->options = options;
 	festate->cstate = cstate;
+	festate->anum_rowid = anum_rowid;
+	festate->lineno = 1;
 
 	node->fdw_state = (void *) festate;
 }
@@ -639,7 +757,16 @@ fileIterateForeignScan(ForeignScanState *node)
 						 slot->tts_values, slot->tts_isnull,
 						 NULL);
 	if (found)
+	{
+		if (festate->anum_rowid != InvalidAttrNumber)
+		{
+			slot->tts_values[festate->anum_rowid - 1]
+				= Int32GetDatum(festate->lineno);
+			slot->tts_isnull[festate->anum_rowid - 1] = false;
+		}
 		ExecStoreVirtualTuple(slot);
+		festate->lineno++;
+	}
 
 	/* Remove error callback. */
 	error_context_stack = errcontext.previous;
@@ -662,6 +789,7 @@ fileReScanForeignScan(ForeignScanState *node)
 									festate->filename,
 									NIL,
 									festate->options);
+	festate->lineno = 1;
 }
 
 /*
@@ -717,6 +845,44 @@ fileAnalyzeForeignTable(Relation relation,
 	return true;
 }
 
+static void
+fileBeginForeignModify(CmdType operation,
+					   ResultRelInfo *resultRelInfo,
+					   EState *estate,
+					   Plan *subplan,
+					   int eflags)
+{
+	elog(INFO, "fdw_file: BeginForeignModify method");
+}
+
+static int
+fileExecForeignInsert(ResultRelInfo *resultRelInfo, HeapTuple tuple)
+{
+	elog(INFO, "fdw_file: INSERT");
+	return 1;
+}
+
+static int
+fileExecForeignDelete(ResultRelInfo *resultRelInfo, Datum rowid)
+{
+	elog(INFO, "fdw_file: DELETE (lineno = %u)", DatumGetInt32(rowid));
+	return 1;
+}
+
+static int
+fileExecForeignUpdate(ResultRelInfo *resultRelInfo,
+					  Datum rowid, HeapTuple tuple)
+{
+	elog(INFO, "fdw_file: UPDATE (lineno = %u)", DatumGetInt32(rowid));
+	return 1;
+}
+
+static void
+fileEndForeignModify(ResultRelInfo *resultRelInfo)
+{
+	elog(INFO, "fdw_file: EndForeignModify method");
+}
+
 /*
  * check_selective_binary_conversion
  *
@@ -789,8 +955,8 @@ check_selective_binary_conversion(RelOptInfo *baserel,
 			break;
 		}
 
-		/* Ignore system attributes. */
-		if (attnum < 0)
+		/* Ignore system or pseudo attributes. */
+		if (attnum < 0 || attnum > RelationGetNumberOfAttributes(rel))
 			continue;
 
 		/* Get user attributes. */
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 4d3ed9c..c67c051 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -2132,9 +2132,10 @@ ExecASDeleteTriggers(EState *estate, ResultRelInfo *relinfo)
 bool
 ExecBRDeleteTriggers(EState *estate, EPQState *epqstate,
 					 ResultRelInfo *relinfo,
-					 ItemPointer tupleid)
+					 Datum rowid)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
+	ItemPointer tupleid = (ItemPointer)DatumGetPointer(rowid);
 	bool		result = true;
 	TriggerData LocTriggerData;
 	HeapTuple	trigtuple;
@@ -2190,9 +2191,10 @@ ExecBRDeleteTriggers(EState *estate, EPQState *epqstate,
 
 void
 ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
-					 ItemPointer tupleid)
+					 Datum rowid)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
+	ItemPointer	tupleid = (ItemPointer)DatumGetPointer(rowid);
 
 	if (trigdesc && trigdesc->trig_delete_after_row)
 	{
@@ -2317,11 +2319,12 @@ ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
 TupleTableSlot *
 ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
 					 ResultRelInfo *relinfo,
-					 ItemPointer tupleid, TupleTableSlot *slot)
+					 Datum rowid, TupleTableSlot *slot)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 	HeapTuple	slottuple = ExecMaterializeSlot(slot);
 	HeapTuple	newtuple = slottuple;
+	ItemPointer	tupleid = (ItemPointer)DatumGetPointer(rowid);
 	TriggerData LocTriggerData;
 	HeapTuple	trigtuple;
 	HeapTuple	oldtuple;
@@ -2414,10 +2417,11 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
 
 void
 ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
-					 ItemPointer tupleid, HeapTuple newtuple,
+					 Datum rowid, HeapTuple newtuple,
 					 List *recheckIndexes)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
+	ItemPointer	tupleid = (ItemPointer)DatumGetPointer(rowid);
 
 	if (trigdesc && trigdesc->trig_update_after_row)
 	{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index d966be5..c701fe7 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"
@@ -934,6 +935,7 @@ void
 CheckValidResultRel(Relation resultRel, CmdType operation)
 {
 	TriggerDesc *trigDesc = resultRel->trigdesc;
+	FdwRoutine	*fdwroutine;
 
 	switch (resultRel->rd_rel->relkind)
 	{
@@ -985,10 +987,34 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
 			}
 			break;
 		case RELKIND_FOREIGN_TABLE:
-			ereport(ERROR,
-					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("cannot change foreign table \"%s\"",
-							RelationGetRelationName(resultRel))));
+			fdwroutine = GetFdwRoutineByRelId(RelationGetRelid(resultRel));
+			switch (operation)
+			{
+				case CMD_INSERT:
+					if (!fdwroutine || !fdwroutine->ExecForeignInsert)
+						ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("cannot insert into foreign table \"%s\"",
+									RelationGetRelationName(resultRel))));
+					break;
+				case CMD_UPDATE:
+					if (!fdwroutine || !fdwroutine->ExecForeignUpdate)
+						ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("cannot update foreign table \"%s\"",
+									RelationGetRelationName(resultRel))));
+					break;
+				case CMD_DELETE:
+					if (!fdwroutine || !fdwroutine->ExecForeignDelete)
+						ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("cannot delete from foreign table \"%s\"",
+									RelationGetRelationName(resultRel))));
+					break;
+				default:
+					elog(ERROR, "unrecognized CmdType: %d", (int) operation);
+					break;
+			}
 			break;
 		default:
 			ereport(ERROR,
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 9204859..1ee626f 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -25,6 +25,7 @@
 #include "executor/executor.h"
 #include "executor/nodeForeignscan.h"
 #include "foreign/fdwapi.h"
+#include "nodes/nodeFuncs.h"
 #include "utils/rel.h"
 
 static TupleTableSlot *ForeignNext(ForeignScanState *node);
@@ -93,6 +94,133 @@ ExecForeignScan(ForeignScanState *node)
 					(ExecScanRecheckMtd) ForeignRecheck);
 }
 
+/*
+ * pseudo_column_walker
+ *
+ * helper routine of GetPseudoTupleDesc. It pulls Var nodes that reference
+ * pseudo columns from targetlis of the relation
+ */
+typedef struct
+{
+	Relation	relation;
+	Index		varno;
+	List	   *pcolumns;
+	AttrNumber	max_attno;
+} pseudo_column_walker_context;
+
+static bool
+pseudo_column_walker(Node *node, pseudo_column_walker_context *context)
+{
+	if (node == NULL)
+		return false;
+	if (IsA(node, Var))
+	{
+		Var		   *var = (Var *) node;
+		ListCell   *cell;
+
+		if (var->varno == context->varno && var->varlevelsup == 0 &&
+			var->varattno > RelationGetNumberOfAttributes(context->relation))
+		{
+			foreach (cell, context->pcolumns)
+			{
+				Var	   *temp = lfirst(cell);
+
+				if (temp->varattno == var->varattno)
+				{
+					if (!equal(var, temp))
+						elog(ERROR, "asymmetric pseudo column appeared");
+					break;
+				}
+			}
+			if (!cell)
+			{
+				context->pcolumns = lappend(context->pcolumns, var);
+				if (var->varattno > context->max_attno)
+					context->max_attno = var->varattno;
+			}
+		}
+		return false;
+	}
+
+	/* Should not find an unplanned subquery */
+	Assert(!IsA(node, Query));
+
+	return expression_tree_walker(node, pseudo_column_walker,
+								  (void *)context);
+}
+
+/*
+ * GetPseudoTupleDesc
+ *
+ * It generates TupleDesc structure including pseudo-columns if required.
+ */
+static TupleDesc
+GetPseudoTupleDesc(ForeignScan *node, Relation relation)
+{
+	pseudo_column_walker_context context;
+	List	   *target_list = node->scan.plan.targetlist;
+	TupleDesc	tupdesc;
+	AttrNumber	attno;
+	ListCell   *cell;
+	ListCell   *prev;
+	bool		hasoid;
+
+	context.relation = relation;
+	context.varno = node->scan.scanrelid;
+	context.pcolumns = NIL;
+	context.max_attno = -1;
+
+	pseudo_column_walker((Node *)target_list, (void *)&context);
+	Assert(context.max_attno > RelationGetNumberOfAttributes(relation));
+
+	hasoid = RelationGetForm(relation)->relhasoids;
+	tupdesc = CreateTemplateTupleDesc(context.max_attno, hasoid);
+
+	for (attno = 1; attno <= context.max_attno; attno++)
+	{
+		/* case of regular columns */
+		if (attno <= RelationGetNumberOfAttributes(relation))
+		{
+			memcpy(tupdesc->attrs[attno - 1],
+				   RelationGetDescr(relation)->attrs[attno - 1],
+				   ATTRIBUTE_FIXED_PART_SIZE);
+			continue;
+		}
+
+		/* case of pseudo columns */
+		prev = NULL;
+		foreach (cell, context.pcolumns)
+		{
+			Var	   *var = lfirst(cell);
+
+			if (var->varattno == attno)
+			{
+				char		namebuf[NAMEDATALEN];
+
+				snprintf(namebuf, sizeof(namebuf),
+						 "pseudo_column_%d", attno);
+
+				TupleDescInitEntry(tupdesc,
+								   attno,
+								   namebuf,
+								   var->vartype,
+								   var->vartypmod,
+								   0);
+				TupleDescInitEntryCollation(tupdesc,
+											attno,
+											var->varcollid);
+				context.pcolumns
+					= list_delete_cell(context.pcolumns, cell, prev);
+				break;
+			}
+			prev = cell;
+		}
+		if (!cell)
+			elog(ERROR, "pseudo column %d of %s not in target list",
+				 attno, RelationGetRelationName(relation));
+	}
+	return tupdesc;
+}
 
 /* ----------------------------------------------------------------
  *		ExecInitForeignScan
@@ -103,6 +231,7 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
 {
 	ForeignScanState *scanstate;
 	Relation	currentRelation;
+	TupleDesc	tupdesc;
 	FdwRoutine *fdwroutine;
 
 	/* check for unsupported flags */
@@ -149,7 +278,12 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
 	/*
 	 * get the scan type from the relation descriptor.
 	 */
-	ExecAssignScanType(&scanstate->ss, RelationGetDescr(currentRelation));
+	if (node->fsPseudoCol)
+		tupdesc = GetPseudoTupleDesc(node, currentRelation);
+	else
+		tupdesc = RelationGetDescr(currentRelation);
+
+	ExecAssignScanType(&scanstate->ss, tupdesc);
 
 	/*
 	 * Initialize result tuple type and projection info.
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 26a59d0..647faf4 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"
@@ -170,6 +171,7 @@ ExecInsert(TupleTableSlot *slot,
 	Relation	resultRelationDesc;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
+	int			num_rows = 1;
 
 	/*
 	 * get the heap tuple out of the tuple table slot, making sure we have a
@@ -225,6 +227,14 @@ ExecInsert(TupleTableSlot *slot,
 
 		newId = InvalidOid;
 	}
+	else if (resultRelInfo->ri_fdwroutine)
+	{
+		FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+		num_rows = fdwroutine->ExecForeignInsert(resultRelInfo, tuple);
+
+		newId = InvalidOid;
+	}
 	else
 	{
 		/*
@@ -252,7 +262,7 @@ ExecInsert(TupleTableSlot *slot,
 
 	if (canSetTag)
 	{
-		(estate->es_processed)++;
+		(estate->es_processed) += num_rows;
 		estate->es_lastoid = newId;
 		setLastTid(&(tuple->t_self));
 	}
@@ -285,7 +295,7 @@ ExecInsert(TupleTableSlot *slot,
  * ----------------------------------------------------------------
  */
 static TupleTableSlot *
-ExecDelete(ItemPointer tupleid,
+ExecDelete(Datum rowid,
 		   HeapTupleHeader oldtuple,
 		   TupleTableSlot *planSlot,
 		   EPQState *epqstate,
@@ -297,6 +307,7 @@ ExecDelete(ItemPointer tupleid,
 	HTSU_Result result;
 	ItemPointerData update_ctid;
 	TransactionId update_xmax;
+	int			num_rows = 1;
 
 	/*
 	 * get information on the (current) result relation
@@ -311,7 +322,7 @@ ExecDelete(ItemPointer tupleid,
 		bool		dodelete;
 
 		dodelete = ExecBRDeleteTriggers(estate, epqstate, resultRelInfo,
-										tupleid);
+										rowid);
 
 		if (!dodelete)			/* "do nothing" */
 			return NULL;
@@ -335,8 +346,16 @@ ExecDelete(ItemPointer tupleid,
 		if (!dodelete)			/* "do nothing" */
 			return NULL;
 	}
+	else if (resultRelInfo->ri_fdwroutine)
+	{
+		FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+		num_rows = fdwroutine->ExecForeignDelete(resultRelInfo, rowid);
+	}
 	else
 	{
+		ItemPointer	tupleid = DatumGetPointer(rowid);
+
 		/*
 		 * delete the tuple
 		 *
@@ -401,10 +420,10 @@ ldelete:;
 	}
 
 	if (canSetTag)
-		(estate->es_processed)++;
+		(estate->es_processed) += num_rows;
 
 	/* AFTER ROW DELETE Triggers */
-	ExecARDeleteTriggers(estate, resultRelInfo, tupleid);
+	ExecARDeleteTriggers(estate, resultRelInfo, rowid);
 
 	/* Process RETURNING if present */
 	if (resultRelInfo->ri_projectReturning)
@@ -428,7 +447,8 @@ ldelete:;
 		}
 		else
 		{
-			deltuple.t_self = *tupleid;
+			ItemPointerCopy((ItemPointer)DatumGetPointer(rowid),
+							&deltuple.t_self);
 			if (!heap_fetch(resultRelationDesc, SnapshotAny,
 							&deltuple, &delbuffer, false, NULL))
 				elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
@@ -470,7 +490,7 @@ ldelete:;
  * ----------------------------------------------------------------
  */
 static TupleTableSlot *
-ExecUpdate(ItemPointer tupleid,
+ExecUpdate(Datum rowid,
 		   HeapTupleHeader oldtuple,
 		   TupleTableSlot *slot,
 		   TupleTableSlot *planSlot,
@@ -485,6 +505,7 @@ ExecUpdate(ItemPointer tupleid,
 	ItemPointerData update_ctid;
 	TransactionId update_xmax;
 	List	   *recheckIndexes = NIL;
+	int			num_rows = 1;
 
 	/*
 	 * abort the operation if not running transactions
@@ -509,7 +530,7 @@ ExecUpdate(ItemPointer tupleid,
 		resultRelInfo->ri_TrigDesc->trig_update_before_row)
 	{
 		slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
-									tupleid, slot);
+									rowid, slot);
 
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
@@ -539,8 +560,16 @@ ExecUpdate(ItemPointer tupleid,
 		/* trigger might have changed tuple */
 		tuple = ExecMaterializeSlot(slot);
 	}
+	else if (resultRelInfo->ri_fdwroutine)
+	{
+		FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+		num_rows = fdwroutine->ExecForeignUpdate(resultRelInfo, rowid, tuple);
+	}
 	else
 	{
+		ItemPointer	tupleid = DatumGetPointer(rowid);
+
 		/*
 		 * Check the constraints of the tuple
 		 *
@@ -630,10 +659,10 @@ lreplace:;
 	}
 
 	if (canSetTag)
-		(estate->es_processed)++;
+		(estate->es_processed) += num_rows;
 
 	/* AFTER ROW UPDATE Triggers */
-	ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple,
+	ExecARUpdateTriggers(estate, resultRelInfo, rowid, tuple,
 						 recheckIndexes);
 
 	list_free(recheckIndexes);
@@ -714,6 +743,7 @@ ExecModifyTable(ModifyTableState *node)
 	TupleTableSlot *planSlot;
 	ItemPointer tupleid = NULL;
 	ItemPointerData tuple_ctid;
+	Datum		rowid;
 	HeapTupleHeader oldtuple = NULL;
 
 	/*
@@ -802,17 +832,19 @@ ExecModifyTable(ModifyTableState *node)
 		if (junkfilter != NULL)
 		{
 			/*
-			 * extract the 'ctid' or 'wholerow' junk attribute.
+			 * extract the 'ctid', 'rowid' or 'wholerow' junk attribute.
 			 */
 			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,
+												 junkfilter->jf_junkRowidNo,
 												 &isNull);
 					/* shouldn't ever get a null result... */
 					if (isNull)
@@ -820,13 +852,33 @@ ExecModifyTable(ModifyTableState *node)
 
 					tupleid = (ItemPointer) DatumGetPointer(datum);
 					tuple_ctid = *tupleid;		/* be sure we don't free
-												 * ctid!! */
-					tupleid = &tuple_ctid;
+												 * ctid ! */
+					rowid = PointerGetDatum(&tuple_ctid);
+				}
+				else if (relkind == RELKIND_FOREIGN_TABLE)
+				{
+					datum = ExecGetJunkAttribute(slot,
+												 junkfilter->jf_junkRowidNo,
+												 &isNull);
+					/* shouldn't ever get a null result... */
+					if (isNull)
+						elog(ERROR, "rowid is NULL");
+
+					rowid = datum;
+
+					datum = ExecGetJunkAttribute(slot,
+												 junkfilter->jf_junkRecordNo,
+												 &isNull);
+					/* shouldn't ever get a null result... */
+					if (isNull)
+						elog(ERROR, "wholerow is NULL");
+
+					oldtuple = DatumGetHeapTupleHeader(datum);
 				}
 				else
 				{
 					datum = ExecGetJunkAttribute(slot,
-												 junkfilter->jf_junkAttNo,
+												 junkfilter->jf_junkRecordNo,
 												 &isNull);
 					/* shouldn't ever get a null result... */
 					if (isNull)
@@ -849,11 +901,11 @@ ExecModifyTable(ModifyTableState *node)
 				slot = ExecInsert(slot, planSlot, estate, node->canSetTag);
 				break;
 			case CMD_UPDATE:
-				slot = ExecUpdate(tupleid, oldtuple, slot, planSlot,
+				slot = ExecUpdate(rowid, oldtuple, slot, planSlot,
 								&node->mt_epqstate, estate, node->canSetTag);
 				break;
 			case CMD_DELETE:
-				slot = ExecDelete(tupleid, oldtuple, planSlot,
+				slot = ExecDelete(rowid, oldtuple, planSlot,
 								&node->mt_epqstate, estate, node->canSetTag);
 				break;
 			default:
@@ -965,6 +1017,22 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		estate->es_result_relation_info = resultRelInfo;
 		mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
 
+		/*
+		 * Also tells FDW extensions to init the plan for this result rel
+		 */
+		if (RelationGetForm(resultRelInfo->ri_RelationDesc)->relkind == RELKIND_FOREIGN_TABLE)
+		{
+			Oid		relid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+			FdwRoutine *fdwroutine = GetFdwRoutineByRelId(relid);
+
+			Assert(fdwroutine != NULL);
+			resultRelInfo->ri_fdwroutine = fdwroutine;
+			resultRelInfo->ri_fdw_state = NULL;
+
+			if (fdwroutine->BeginForeignModify)
+				fdwroutine->BeginForeignModify(operation, resultRelInfo,
+											   estate, subplan, eflags);
+		}
 		resultRelInfo++;
 		i++;
 	}
@@ -1106,6 +1174,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			for (i = 0; i < nplans; i++)
 			{
 				JunkFilter *j;
+				char		relkind =
+				    RelationGetForm(resultRelInfo->ri_RelationDesc)->relkind;
 
 				subplan = mtstate->mt_plans[i]->plan;
 				if (operation == CMD_INSERT || operation == CMD_UPDATE)
@@ -1119,16 +1189,27 @@ 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)
+					if (relkind == RELKIND_RELATION)
 					{
-						j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
-						if (!AttributeNumberIsValid(j->jf_junkAttNo))
+						j->jf_junkRowidNo = ExecFindJunkAttribute(j, "ctid");
+						if (!AttributeNumberIsValid(j->jf_junkRowidNo))
 							elog(ERROR, "could not find junk ctid column");
 					}
+					else if (relkind == RELKIND_FOREIGN_TABLE)
+					{
+						j->jf_junkRowidNo = ExecFindJunkAttribute(j, "rowid");
+						if (!AttributeNumberIsValid(j->jf_junkRowidNo))
+							elog(ERROR, "could not find junk rowid column");
+						j->jf_junkRecordNo
+							= ExecFindJunkAttribute(j, "record");
+						if (!AttributeNumberIsValid(j->jf_junkRecordNo))
+							elog(ERROR, "could not find junk wholerow column");
+					}
 					else
 					{
-						j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
-						if (!AttributeNumberIsValid(j->jf_junkAttNo))
+						j->jf_junkRecordNo
+							= ExecFindJunkAttribute(j, "wholerow");
+						if (!AttributeNumberIsValid(j->jf_junkRecordNo))
 							elog(ERROR, "could not find junk wholerow column");
 					}
 				}
@@ -1182,6 +1263,21 @@ ExecEndModifyTable(ModifyTableState *node)
 {
 	int			i;
 
+	/* Let the FDW shut dowm */
+	for (i=0; i < node->ps.state->es_num_result_relations; i++)
+	{
+		ResultRelInfo  *resultRelInfo
+			= &node->ps.state->es_result_relations[i];
+
+		if (resultRelInfo->ri_fdwroutine)
+		{
+			FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+			if (fdwroutine->EndForeignModify)
+				fdwroutine->EndForeignModify(resultRelInfo);
+		}
+	}
+
 	/*
 	 * Free the exprcontext
 	 */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index f34f704..3983e87 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -594,6 +594,7 @@ _copyForeignScan(const ForeignScan *from)
 	COPY_NODE_FIELD(fdw_exprs);
 	COPY_NODE_FIELD(fdw_private);
 	COPY_SCALAR_FIELD(fsSystemCol);
+	COPY_SCALAR_FIELD(fsPseudoCol);
 
 	return newnode;
 }
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 02a0f62..6f16ef0 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -562,6 +562,7 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
 	WRITE_NODE_FIELD(fdw_exprs);
 	WRITE_NODE_FIELD(fdw_private);
 	WRITE_BOOL_FIELD(fsSystemCol);
+	WRITE_BOOL_FIELD(fsPseudoCol);
 }
 
 static void
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 030f420..ff3b9ff 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -39,6 +39,7 @@
 #include "parser/parse_clause.h"
 #include "parser/parsetree.h"
 #include "utils/lsyscache.h"
+#include "utils/rel.h"
 
 
 static Plan *create_plan_recurse(PlannerInfo *root, Path *best_path);
@@ -1943,6 +1944,8 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 	RelOptInfo *rel = best_path->path.parent;
 	Index		scan_relid = rel->relid;
 	RangeTblEntry *rte;
+	Relation	relation;
+	AttrNumber	num_attrs;
 	int			i;
 
 	/* it should be a base rel... */
@@ -2001,6 +2004,22 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 		}
 	}
 
+	/*
+	 * Also, detect whether any pseudo columns are requested from rel.
+	 */
+	relation = heap_open(rte->relid, NoLock);
+	scan_plan->fsPseudoCol = false;
+	num_attrs = RelationGetNumberOfAttributes(relation);
+	for (i = num_attrs + 1; i <= rel->max_attr; i++)
+	{
+		if (!bms_is_empty(rel->attr_needed[i - rel->min_attr]))
+		{
+			scan_plan->fsPseudoCol = true;
+			break;
+		}
+	}
+	heap_close(relation, NoLock);
+
 	return scan_plan;
 }
 
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 9565e2d..740bb1e 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -82,7 +82,7 @@ static void check_hashjoinable(RestrictInfo *restrictinfo);
  * "other rel" RelOptInfos for the members of any appendrels we find here.)
  */
 void
-add_base_rels_to_query(PlannerInfo *root, Node *jtnode)
+add_base_rels_to_query(PlannerInfo *root, List *tlist, Node *jtnode)
 {
 	if (jtnode == NULL)
 		return;
@@ -90,7 +90,7 @@ add_base_rels_to_query(PlannerInfo *root, Node *jtnode)
 	{
 		int			varno = ((RangeTblRef *) jtnode)->rtindex;
 
-		(void) build_simple_rel(root, varno, RELOPT_BASEREL);
+		(void) build_simple_rel(root, varno, tlist, RELOPT_BASEREL);
 	}
 	else if (IsA(jtnode, FromExpr))
 	{
@@ -98,14 +98,14 @@ add_base_rels_to_query(PlannerInfo *root, Node *jtnode)
 		ListCell   *l;
 
 		foreach(l, f->fromlist)
-			add_base_rels_to_query(root, lfirst(l));
+			add_base_rels_to_query(root, tlist, lfirst(l));
 	}
 	else if (IsA(jtnode, JoinExpr))
 	{
 		JoinExpr   *j = (JoinExpr *) jtnode;
 
-		add_base_rels_to_query(root, j->larg);
-		add_base_rels_to_query(root, j->rarg);
+		add_base_rels_to_query(root, tlist, j->larg);
+		add_base_rels_to_query(root, tlist, j->rarg);
 	}
 	else
 		elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
index c2488a4..2d292dd 100644
--- a/src/backend/optimizer/plan/planmain.c
+++ b/src/backend/optimizer/plan/planmain.c
@@ -163,7 +163,7 @@ query_planner(PlannerInfo *root, List *tlist,
 	 * rangetable may contain RTEs for rels not actively part of the query,
 	 * for example views.  We don't want to make RelOptInfos for them.
 	 */
-	add_base_rels_to_query(root, (Node *) parse->jointree);
+	add_base_rels_to_query(root, tlist, (Node *) parse->jointree);
 
 	/*
 	 * Examine the targetlist and join tree, adding entries to baserel
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 385e646..7951762 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -3310,7 +3310,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 	setup_simple_rel_arrays(root);
 
 	/* Build RelOptInfo */
-	rel = build_simple_rel(root, 1, RELOPT_BASEREL);
+	rel = build_simple_rel(root, 1, NIL, RELOPT_BASEREL);
 
 	/* Locate IndexOptInfo for the target index */
 	indexInfo = NULL;
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 9e154e1..391721e 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -236,7 +236,8 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 		 * used for anything here, but it carries the subroot data structures
 		 * forward to setrefs.c processing.
 		 */
-		rel = build_simple_rel(root, rtr->rtindex, RELOPT_BASEREL);
+		rel = build_simple_rel(root, rtr->rtindex, refnames_tlist,
+							   RELOPT_BASEREL);
 
 		/* plan_params should not be in use in current query level */
 		Assert(root->plan_params == NIL);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 25f8785..3b25ab5 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -25,6 +25,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/heap.h"
+#include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/clauses.h"
@@ -80,7 +81,7 @@ static List *build_index_tlist(PlannerInfo *root, IndexOptInfo *index,
  */
 void
 get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
-				  RelOptInfo *rel)
+				  List *tlist, RelOptInfo *rel)
 {
 	Index		varno = rel->relid;
 	Relation	relation;
@@ -104,6 +105,26 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 	rel->max_attr = RelationGetNumberOfAttributes(relation);
 	rel->reltablespace = RelationGetForm(relation)->reltablespace;
 
+	/*
+	 * Adjust width of attr_needed slot in case when FDW extension wants
+	 * or needs to return pseudo-columns also, not only columns in its
+	 * table definition.
+	 * GetForeignRelInfo, an optional FDW handler, enables FDW extension
+	 * to save properties of pseudo-column on its private field.
+	 * When foreign-table is the target of UPDATE/DELETE, query-rewritter
+	 * injects "rowid" pseudo-column to track remote row to be modified,
+	 * so FDW has to track which varattno shall perform as "rowid".
+	 */
+	if (RelationGetForm(relation)->relkind == RELKIND_FOREIGN_TABLE)
+	{
+		FdwRoutine *fdwroutine = GetFdwRoutineByRelId(relationObjectId);
+
+		if (fdwroutine->GetForeignRelInfo)
+			fdwroutine->GetForeignRelInfo(root, rel,
+										  relationObjectId,
+										  inhparent, tlist);
+	}
+
 	Assert(rel->max_attr >= rel->min_attr);
 	rel->attr_needed = (Relids *)
 		palloc0((rel->max_attr - rel->min_attr + 1) * sizeof(Relids));
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index f724714..84b674c 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -80,7 +80,8 @@ setup_simple_rel_arrays(PlannerInfo *root)
  *	  Construct a new RelOptInfo for a base relation or 'other' relation.
  */
 RelOptInfo *
-build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
+build_simple_rel(PlannerInfo *root, int relid, List *tlist,
+				 RelOptKind reloptkind)
 {
 	RelOptInfo *rel;
 	RangeTblEntry *rte;
@@ -133,7 +134,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
 	{
 		case RTE_RELATION:
 			/* Table --- retrieve statistics from the system catalogs */
-			get_relation_info(root, rte->relid, rte->inh, rel);
+			get_relation_info(root, rte->relid, rte->inh, tlist, rel);
 			break;
 		case RTE_SUBQUERY:
 		case RTE_FUNCTION:
@@ -180,7 +181,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
 			if (appinfo->parent_relid != relid)
 				continue;
 
-			(void) build_simple_rel(root, appinfo->child_relid,
+			(void) build_simple_rel(root, appinfo->child_relid, tlist,
 									RELOPT_OTHER_MEMBER_REL);
 		}
 	}
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 8f75948..469dbf9 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1163,7 +1163,10 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 					Relation target_relation)
 {
 	Var		   *var;
-	const char *attrname;
+	List	   *varList;
+	List	   *attNameList;
+	ListCell   *cell1;
+	ListCell   *cell2;
 	TargetEntry *tle;
 
 	if (target_relation->rd_rel->relkind == RELKIND_RELATION)
@@ -1177,8 +1180,35 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 					  -1,
 					  InvalidOid,
 					  0);
+		varList = list_make1(var);
+		attNameList = list_make1("ctid");
+	}
+	else if (target_relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+	{
+		/*
+		 * Emit Rowid so that executor can find the row to update or delete.
+		 */
+		var = makeVar(parsetree->resultRelation,
+					  RelationGetNumberOfAttributes(target_relation) + 1,
+					  VOIDOID,
+					  -1,
+					  InvalidOid,
+					  0);
+		varList = list_make1(var);
 
-		attrname = "ctid";
+		/*
+		 * Emit generic record Var so that executor will have the "old" view
+		 * row to pass the RETURNING clause (or upcoming triggers).
+		 */
+		var = makeVar(parsetree->resultRelation,
+					  InvalidAttrNumber,
+					  RECORDOID,
+					  -1,
+					  InvalidOid,
+					  0);
+		varList = lappend(varList, var);
+
+		attNameList = list_make2("rowid", "record");
 	}
 	else
 	{
@@ -1190,16 +1220,21 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 							  parsetree->resultRelation,
 							  0,
 							  false);
-
-		attrname = "wholerow";
+		varList = list_make1(var);
+		attNameList = list_make1("wholerow");
 	}
 
-	tle = makeTargetEntry((Expr *) var,
-						  list_length(parsetree->targetList) + 1,
-						  pstrdup(attrname),
-						  true);
-
-	parsetree->targetList = lappend(parsetree->targetList, tle);
+	/*
+	 * Append them to targetList
+	 */
+	forboth (cell1, varList, cell2, attNameList)
+	{
+		tle = makeTargetEntry((Expr *)lfirst(cell1),
+							  list_length(parsetree->targetList) + 1,
+							  pstrdup((const char *)lfirst(cell2)),
+							  true);
+		parsetree->targetList = lappend(parsetree->targetList, tle);
+	}
 }
 
 
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index e790b9d..8370e05 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -147,10 +147,10 @@ extern void ExecASDeleteTriggers(EState *estate,
 extern bool ExecBRDeleteTriggers(EState *estate,
 					 EPQState *epqstate,
 					 ResultRelInfo *relinfo,
-					 ItemPointer tupleid);
+					 Datum rowid);
 extern void ExecARDeleteTriggers(EState *estate,
 					 ResultRelInfo *relinfo,
-					 ItemPointer tupleid);
+					 Datum rowid);
 extern bool ExecIRDeleteTriggers(EState *estate,
 					 ResultRelInfo *relinfo,
 					 HeapTuple trigtuple);
@@ -161,11 +161,11 @@ extern void ExecASUpdateTriggers(EState *estate,
 extern TupleTableSlot *ExecBRUpdateTriggers(EState *estate,
 					 EPQState *epqstate,
 					 ResultRelInfo *relinfo,
-					 ItemPointer tupleid,
+					 Datum rowid,
 					 TupleTableSlot *slot);
 extern void ExecARUpdateTriggers(EState *estate,
 					 ResultRelInfo *relinfo,
-					 ItemPointer tupleid,
+					 Datum rowid,
 					 HeapTuple newtuple,
 					 List *recheckIndexes);
 extern TupleTableSlot *ExecIRUpdateTriggers(EState *estate,
diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
index 721cd25..8b29532 100644
--- a/src/include/foreign/fdwapi.h
+++ b/src/include/foreign/fdwapi.h
@@ -22,6 +22,11 @@ struct ExplainState;
 /*
  * Callback function signatures --- see fdwhandler.sgml for more info.
  */
+typedef void (*GetForeignRelInfo_function) (PlannerInfo *root,
+											RelOptInfo *baserel,
+											Oid foreigntableid,
+											bool inhparent,
+											List *targetList);
 
 typedef void (*GetForeignRelSize_function) (PlannerInfo *root,
 														RelOptInfo *baserel,
@@ -59,6 +64,20 @@ typedef bool (*AnalyzeForeignTable_function) (Relation relation,
 												 AcquireSampleRowsFunc *func,
 													BlockNumber *totalpages);
 
+typedef void (*BeginForeignModify_function) (CmdType operation,
+											 ResultRelInfo *resultRelInfo,
+											 EState *estate,
+											 Plan *subplan,
+											 int eflags);
+typedef int	(*ExecForeignInsert_function) (ResultRelInfo *resultRelInfo,
+										   HeapTuple tuple);
+typedef int	(*ExecForeignDelete_function) (ResultRelInfo *resultRelInfo,
+										   Datum rowid);
+typedef int	(*ExecForeignUpdate_function) (ResultRelInfo *resultRelInfo,
+										   Datum rowid,
+										   HeapTuple tuple);
+typedef void (*EndForeignModify_function) (ResultRelInfo *resultRelInfo);
+
 /*
  * FdwRoutine is the struct returned by a foreign-data wrapper's handler
  * function.  It provides pointers to the callback functions needed by the
@@ -90,6 +109,12 @@ typedef struct FdwRoutine
 	 * not provided.
 	 */
 	AnalyzeForeignTable_function AnalyzeForeignTable;
+	GetForeignRelInfo_function GetForeignRelInfo;
+	BeginForeignModify_function	BeginForeignModify;
+	ExecForeignInsert_function ExecForeignInsert;
+	ExecForeignDelete_function ExecForeignDelete;
+	ExecForeignUpdate_function ExecForeignUpdate;
+	EndForeignModify_function EndForeignModify;
 } FdwRoutine;
 
 
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index fec07b8..d8fa299 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -268,9 +268,11 @@ typedef struct ProjectionInfo
  *						attribute numbers of the "original" tuple and the
  *						attribute numbers of the "clean" tuple.
  *	  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
+ *	  jf_junkRowidNo:	not used by junkfilter code.  Can be used by caller
+ *						to remember the attno of to track a particular tuple
+ *						being updated or deleted.
  *						(execMain.c stores the "ctid" attno here).
+ *	  jf_junkRecordNo:	Also, the attno of whole-row reference.
  * ----------------
  */
 typedef struct JunkFilter
@@ -280,7 +282,8 @@ typedef struct JunkFilter
 	TupleDesc	jf_cleanTupType;
 	AttrNumber *jf_cleanMap;
 	TupleTableSlot *jf_resultSlot;
-	AttrNumber	jf_junkAttNo;
+	AttrNumber	jf_junkRowidNo;
+	AttrNumber	jf_junkRecordNo;
 } JunkFilter;
 
 /* ----------------
@@ -303,6 +306,8 @@ typedef struct JunkFilter
  *		ConstraintExprs			array of constraint-checking expr states
  *		junkFilter				for removing junk attributes from tuples
  *		projectReturning		for computing a RETURNING list
+ *		fdwroutine				FDW callbacks if foreign table
+ *		fdw_state				opaque state of FDW module, or NULL
  * ----------------
  */
 typedef struct ResultRelInfo
@@ -320,6 +325,8 @@ typedef struct ResultRelInfo
 	List	  **ri_ConstraintExprs;
 	JunkFilter *ri_junkFilter;
 	ProjectionInfo *ri_projectReturning;
+	struct FdwRoutine  *ri_fdwroutine;
+	void	   *ri_fdw_state;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index fb9a863..a724863 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -478,6 +478,7 @@ typedef struct ForeignScan
 	List	   *fdw_exprs;		/* expressions that FDW may evaluate */
 	List	   *fdw_private;	/* private data for FDW */
 	bool		fsSystemCol;	/* true if any "system column" is needed */
+	bool		fsPseudoCol;	/* true if any "pseudo column" is needed */
 } ForeignScan;
 
 
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index de889fb..adfc93d 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -133,7 +133,7 @@ extern Path *reparameterize_path(PlannerInfo *root, Path *path,
  */
 extern void setup_simple_rel_arrays(PlannerInfo *root);
 extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid,
-				 RelOptKind reloptkind);
+			   List *tlist, RelOptKind reloptkind);
 extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid);
 extern RelOptInfo *find_join_rel(PlannerInfo *root, Relids relids);
 extern RelOptInfo *build_join_rel(PlannerInfo *root,
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index e0d04db..c5fce41 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -26,7 +26,7 @@ extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook;
 
 
 extern void get_relation_info(PlannerInfo *root, Oid relationObjectId,
-				  bool inhparent, RelOptInfo *rel);
+				  bool inhparent, List *tlist, RelOptInfo *rel);
 
 extern void estimate_rel_size(Relation rel, int32 *attr_widths,
 				  BlockNumber *pages, double *tuples, double *allvisfrac);
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index c395d42..d8bc7da 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -90,7 +90,8 @@ extern bool is_projection_capable_plan(Plan *plan);
 extern int	from_collapse_limit;
 extern int	join_collapse_limit;
 
-extern void add_base_rels_to_query(PlannerInfo *root, Node *jtnode);
+extern void add_base_rels_to_query(PlannerInfo *root, List *tlist,
+								   Node *jtnode);
 extern void build_base_rel_tlists(PlannerInfo *root, List *final_tlist);
 extern void add_vars_to_targetlist(PlannerInfo *root, List *vars,
 					   Relids where_needed, bool create_new_ph);
#24Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Kohei KaiGai (#23)
1 attachment(s)
Re: [v9.3] writable foreign tables

2012/9/23 Kohei KaiGai <kaigai@kaigai.gr.jp>:

2012/8/29 Kohei KaiGai <kaigai@kaigai.gr.jp>:

2012/8/28 Kohei KaiGai <kaigai@kaigai.gr.jp>:

2012/8/28 Tom Lane <tgl@sss.pgh.pa.us>:

Kohei KaiGai <kaigai@kaigai.gr.jp> writes:

Would it be too invasive to introduce a new pointer in TupleTableSlot
that is NULL for anything but virtual tuples from foreign tables?

I'm not certain whether the duration of TupleTableSlot is enough to
carry a private datum between scan and modify stage.

It's not.

Is it possible to utilize ctid field to move a private pointer?

UPDATEs and DELETEs do not rely on the ctid field of tuples to carry the
TID from scan to modify --- in fact, most of the time what the modify
step is going to get is a "virtual" TupleTableSlot that hasn't even
*got* a physical CTID field.

Instead, the planner arranges for the TID to be carried up as an
explicit resjunk column named ctid. (Currently this is done in
rewriteTargetListUD(), but see also preptlist.c which does some related
things for SELECT FOR UPDATE.)

I'm inclined to think that what we need here is for FDWs to be able to
modify the details of that behavior, at least to the extent of being
able to specify a different data type than TID for the row
identification column.

Hmm. It seems to me a straight-forward solution rather than ab-use
of ctid system column. Probably, cstring data type is more suitable
to carry a private datum between scan and modify stage.

One problem I noticed is how FDW driver returns an extra field that
is in neither system nor regular column.
Number of columns and its data type are defined with TupleDesc of
the target foreign-table, so we also need a feature to extend it on
run-time. For example, FDW driver may have to be able to extend
a "virtual" column with cstring data type, even though the target
foreign table does not have such a column.

I tried to investigate the related routines.

TupleDesc of TupleTableSlot associated with ForeignScanState
is initialized at ExecInitForeignScan as literal.
ExecAssignScanType assigns TupleDesc of the target foreign-
table on tts_tupleDescriptor, "as-is".
It is the reason why IterateForeignScan cannot return a private
datum except for the columns being declared as regular ones.

The attached patch improved its design according to the upthread
discussion. It now got away from ab-use of "ctid" field, and adopts
a concept of pseudo-column to hold row-id with opaque data type
instead.

Pseudo-column is Var reference towards attribute-number larger
than number of attributes on the target relation; thus, it is not
a substantial object. It is normally unavailable to reference such
a larger attribute number because TupleDesc of each ScanState
associated with a particular relation is initialized at ExecInitNode.

The patched ExecInitForeignScan was extended to generate its
own TupleDesc including pseudo-column definitions on the fly,
instead of relation's one, when scan-plan of foreign-table requires
to have pseudo-columns.

Right now, the only possible pseudo-column is "rowid" being
injected at rewriteTargetListUD(). It has no data format
restriction like "ctid" because of VOID data type.
FDW extension can set an appropriate value on the "rowid"
field in addition to contents of regular columns at
IterateForeignScan method, to track which remote row should
be updated or deleted.

Another possible usage of this pseudo-column is push-down
of target-list including complex calculation. It may enable to
move complex mathematical formula into remote devices
(such as GPU device?) instead of just a reference of Var node.

This patch adds a new interface: GetForeignRelInfo being invoked
from get_relation_info() to adjust width of RelOptInfo->attr_needed
according to the target-list which may contain "rowid" pseudo-column.
Some FDW extension may use this interface to push-down a part of
target list into remote side, even though I didn't implement this
feature on file_fdw.

RelOptInfo->max_attr is a good marker whether the plan shall have
pseudo-column reference. Then, ExecInitForeignScan determines
whether it should generate a TupleDesc, or not.

The "rowid" is fetched using ExecGetJunkAttribute as we are currently
doing on regular tables using "ctid", then it shall be delivered to
ExecUpdate or ExecDelete. We can never expect the fist argument of
them now, so "ItemPointer tupleid" redefined to "Datum rowid", and
argument of BR-trigger routines redefined also.

[kaigai@iwashi sepgsql]$ cat ~/testfile.csv
10 aaa
11 bbb
12 ccc
13 ddd
14 eee
15 fff
[kaigai@iwashi sepgsql]$ psql postgres
psql (9.3devel)
Type "help" for help.

postgres=# UPDATE ftbl SET b = md5(b) WHERE a > 12 RETURNING *;
INFO: ftbl is the target relation of UPDATE
INFO: fdw_file: BeginForeignModify method
INFO: fdw_file: UPDATE (lineno = 4)
INFO: fdw_file: UPDATE (lineno = 5)
INFO: fdw_file: UPDATE (lineno = 6)
INFO: fdw_file: EndForeignModify method
a | b
----+----------------------------------
13 | 77963b7a931377ad4ab5ad6a9cd718aa
14 | d2f2297d6e829cd3493aa7de4416a18f
15 | 343d9040a671c45832ee5381860e2996
(3 rows)

UPDATE 3
postgres=# DELETE FROM ftbl WHERE a % 2 = 1 RETURNING *;
INFO: ftbl is the target relation of DELETE
INFO: fdw_file: BeginForeignModify method
INFO: fdw_file: DELETE (lineno = 2)
INFO: fdw_file: DELETE (lineno = 4)
INFO: fdw_file: DELETE (lineno = 6)
INFO: fdw_file: EndForeignModify method
a | b
----+-----
11 | bbb
13 | ddd
15 | fff
(3 rows)

DELETE 3

In addition, there is a small improvement. ExecForeignInsert,
ExecForeignUpdate and ExecForeignDelete get being able
to return number of processed rows; that allows to push-down
whole the statement into remote-side, if it is enough simple
(e.g, delete statement without any condition).

Even though it does not make matter right now, pseudo-columns
should be adjusted when foreign-table is referenced with table
inheritance feature, because an attribute number being enough
large in parent table is not enough large in child table.
We need to fix up them until foreign table feature got inheritance
capability.

I didn't update the documentation stuff because I positioned
the state of this patch as proof-of-concept now. Please note that.

A tiny bit of this patch was updated. I noticed INTERNAL data type
is more suitable to move a private datum from scan-stage to
modify-stage, because its type-length is declared as SIZEOF_POINTER.

Also, I fixed up some obvious compiler warnings.

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

Attachments:

pgsql-v9.3-writable-fdw-poc.v3.patchapplication/octet-stream; name=pgsql-v9.3-writable-fdw-poc.v3.patchDownload
 contrib/file_fdw/file_fdw.c             | 177 +++++++++++++++++++++++++++++++-
 src/backend/commands/trigger.c          |  12 ++-
 src/backend/executor/execMain.c         |  34 +++++-
 src/backend/executor/nodeForeignscan.c  | 136 +++++++++++++++++++++++-
 src/backend/executor/nodeModifyTable.c  | 142 ++++++++++++++++++++-----
 src/backend/nodes/copyfuncs.c           |   1 +
 src/backend/nodes/outfuncs.c            |   1 +
 src/backend/optimizer/plan/createplan.c |  19 ++++
 src/backend/optimizer/plan/initsplan.c  |  10 +-
 src/backend/optimizer/plan/planmain.c   |   2 +-
 src/backend/optimizer/plan/planner.c    |   2 +-
 src/backend/optimizer/prep/prepunion.c  |   3 +-
 src/backend/optimizer/util/plancat.c    |  23 ++++-
 src/backend/optimizer/util/relnode.c    |   7 +-
 src/backend/rewrite/rewriteHandler.c    |  55 ++++++++--
 src/include/commands/trigger.h          |   8 +-
 src/include/foreign/fdwapi.h            |  25 +++++
 src/include/nodes/execnodes.h           |  13 ++-
 src/include/nodes/plannodes.h           |   1 +
 src/include/optimizer/pathnode.h        |   2 +-
 src/include/optimizer/plancat.h         |   2 +-
 src/include/optimizer/planmain.h        |   3 +-
 22 files changed, 609 insertions(+), 69 deletions(-)

diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
index 81fc4e2..36ffe44 100644
--- a/contrib/file_fdw/file_fdw.c
+++ b/contrib/file_fdw/file_fdw.c
@@ -34,6 +34,7 @@
 #include "optimizer/var.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
+#include "utils/lsyscache.h"
 
 PG_MODULE_MAGIC;
 
@@ -87,6 +88,7 @@ typedef struct FileFdwPlanState
 	List	   *options;		/* merged COPY options, excluding filename */
 	BlockNumber pages;			/* estimate of file's physical size */
 	double		ntuples;		/* estimate of number of rows in file */
+	DefElem	   *anum_rowid;		/* attribute number of rowid */
 } FileFdwPlanState;
 
 /*
@@ -97,6 +99,8 @@ typedef struct FileFdwExecutionState
 	char	   *filename;		/* file to read */
 	List	   *options;		/* merged COPY options, excluding filename */
 	CopyState	cstate;			/* state of reading file */
+	int			lineno;			/* pseudo ctid of the file */
+	AttrNumber	anum_rowid;		/* attribute number of rowid */
 } FileFdwExecutionState;
 
 /*
@@ -111,6 +115,10 @@ PG_FUNCTION_INFO_V1(file_fdw_validator);
 /*
  * FDW callback routines
  */
+static void fileGetForeignRelInfo(PlannerInfo *root,
+								  RelOptInfo *baserel,
+								  Oid foreigntableid,
+								  bool inhparent, List *targetList);
 static void fileGetForeignRelSize(PlannerInfo *root,
 					  RelOptInfo *baserel,
 					  Oid foreigntableid);
@@ -131,6 +139,19 @@ static void fileEndForeignScan(ForeignScanState *node);
 static bool fileAnalyzeForeignTable(Relation relation,
 						AcquireSampleRowsFunc *func,
 						BlockNumber *totalpages);
+static void fileBeginForeignModify(CmdType operation,
+								   ResultRelInfo *resultRelInfo,
+								   EState *estate,
+								   Plan *subplan,
+								   int eflags);
+static int	fileExecForeignInsert(ResultRelInfo *resultRelInfo,
+								  HeapTuple tuple);
+static int	fileExecForeignDelete(ResultRelInfo *resultRelInfo,
+								  Datum rowid);
+static int	fileExecForeignUpdate(ResultRelInfo *resultRelInfo,
+								  Datum rowid,
+								  HeapTuple tuple);
+static void fileEndForeignModify(ResultRelInfo *resultRelInfo);
 
 /*
  * Helper functions
@@ -169,7 +190,13 @@ file_fdw_handler(PG_FUNCTION_ARGS)
 	fdwroutine->IterateForeignScan = fileIterateForeignScan;
 	fdwroutine->ReScanForeignScan = fileReScanForeignScan;
 	fdwroutine->EndForeignScan = fileEndForeignScan;
+	fdwroutine->GetForeignRelInfo = fileGetForeignRelInfo;
 	fdwroutine->AnalyzeForeignTable = fileAnalyzeForeignTable;
+	fdwroutine->BeginForeignModify = fileBeginForeignModify;
+	fdwroutine->ExecForeignInsert = fileExecForeignInsert;
+	fdwroutine->ExecForeignDelete = fileExecForeignDelete;
+	fdwroutine->ExecForeignUpdate = fileExecForeignUpdate;
+	fdwroutine->EndForeignModify = fileEndForeignModify;
 
 	PG_RETURN_POINTER(fdwroutine);
 }
@@ -424,6 +451,47 @@ get_file_fdw_attribute_options(Oid relid)
 }
 
 /*
+ * fileGetForeignRelInfo
+ *		Adjust size of baserel->attr_needed according to var references
+ *		within targetList.
+ */
+static void
+fileGetForeignRelInfo(PlannerInfo *root,
+                      RelOptInfo *baserel,
+                      Oid foreigntableid,
+					  bool inhparent, List *targetList)
+{
+	FileFdwPlanState *fdw_private;
+	ListCell   *cell;
+
+	fdw_private = (FileFdwPlanState *) palloc0(sizeof(FileFdwPlanState));
+
+	foreach(cell, targetList)
+	{
+		TargetEntry	*tle = lfirst(cell);
+
+		if (tle->resjunk &&
+			tle->resname && strcmp(tle->resname, "rowid")==0)
+		{
+			Bitmapset  *temp = NULL;
+			AttrNumber	anum_rowid;
+			DefElem	   *defel;
+
+			pull_varattnos((Node *)tle, baserel->relid, &temp);
+			anum_rowid = bms_singleton_member(temp)
+				+ FirstLowInvalidHeapAttributeNumber;
+			/* adjust attr_needed of baserel */
+			if (anum_rowid > baserel->max_attr)
+				baserel->max_attr = anum_rowid;
+			defel = makeDefElem("anum_rowid",
+								(Node *)makeInteger(anum_rowid));
+			fdw_private->anum_rowid = defel;
+		}
+	}
+	baserel->fdw_private = fdw_private;
+}
+
+/*
  * fileGetForeignRelSize
  *		Obtain relation size estimates for a foreign table
  */
@@ -432,16 +500,14 @@ fileGetForeignRelSize(PlannerInfo *root,
 					  RelOptInfo *baserel,
 					  Oid foreigntableid)
 {
-	FileFdwPlanState *fdw_private;
+	FileFdwPlanState *fdw_private = (FileFdwPlanState *) baserel->fdw_private;
 
 	/*
 	 * Fetch options.  We only need filename at this point, but we might as
 	 * well get everything and not need to re-fetch it later in planning.
 	 */
-	fdw_private = (FileFdwPlanState *) palloc(sizeof(FileFdwPlanState));
 	fileGetOptions(foreigntableid,
 				   &fdw_private->filename, &fdw_private->options);
-	baserel->fdw_private = (void *) fdw_private;
 
 	/* Estimate relation size */
 	estimate_size(root, baserel, fdw_private);
@@ -473,6 +539,10 @@ fileGetForeignPaths(PlannerInfo *root,
 		coptions = list_make1(makeDefElem("convert_selectively",
 										  (Node *) columns));
 
+	/* save attribute number of rowid pseudo column */
+	if (fdw_private->anum_rowid)
+		coptions = lappend(coptions, fdw_private->anum_rowid);
+
 	/* Estimate costs */
 	estimate_costs(root, baserel, fdw_private,
 				   &startup_cost, &total_cost);
@@ -513,6 +583,35 @@ fileGetForeignPlan(PlannerInfo *root,
 	Index		scan_relid = baserel->relid;
 
 	/*
+	 * XXX - An evidence FDW module can know what kind of accesses are
+	 * requires on the target relation, if UPDATE, INSERT or DELETE.
+	 * It shall be also utilized to appropriate lock level on FDW
+	 * extensions that performs behalf on real RDBMS.
+	 */
+	if (root->parse->resultRelation == baserel->relid)
+	{
+		switch (root->parse->commandType)
+		{
+			case CMD_INSERT:
+				elog(INFO, "%s is the target relation of INSERT",
+					 get_rel_name(foreigntableid));
+				break;
+			case CMD_UPDATE:
+				elog(INFO, "%s is the target relation of UPDATE",
+					 get_rel_name(foreigntableid));
+				break;
+			case CMD_DELETE:
+				elog(INFO, "%s is the target relation of DELETE",
+                     get_rel_name(foreigntableid));
+				break;
+			default:
+				elog(INFO, "%s is the target relation of ??????",
+                     get_rel_name(foreigntableid));
+				break;
+		}
+	}
+
+	/*
 	 * We have no native ability to evaluate restriction clauses, so we just
 	 * put all the scan_clauses into the plan node's qual list for the
 	 * executor to check.  So all we have to do here is strip RestrictInfo
@@ -566,7 +665,10 @@ fileBeginForeignScan(ForeignScanState *node, int eflags)
 	ForeignScan *plan = (ForeignScan *) node->ss.ps.plan;
 	char	   *filename;
 	List	   *options;
+	ListCell   *cell;
+	ListCell   *prev;
 	CopyState	cstate;
+	AttrNumber	anum_rowid = InvalidAttrNumber;
 	FileFdwExecutionState *festate;
 
 	/*
@@ -582,6 +684,21 @@ fileBeginForeignScan(ForeignScanState *node, int eflags)
 	/* Add any options from the plan (currently only convert_selectively) */
 	options = list_concat(options, plan->fdw_private);
 
+	/* Fetch anum_rowid, if exist in options */
+	prev = NULL;
+	foreach (cell, options)
+	{
+		DefElem	   *defel = lfirst(cell);
+
+		if (strcmp(defel->defname, "anum_rowid") == 0)
+		{
+			anum_rowid = intVal(defel->arg);
+			options = list_delete_cell(options, cell, prev);
+			break;
+		}
+		prev = cell;
+	}
+
 	/*
 	 * Create CopyState from FDW options.  We always acquire all columns, so
 	 * as to match the expected ScanTupleSlot signature.
@@ -599,6 +716,8 @@ fileBeginForeignScan(ForeignScanState *node, int eflags)
 	festate->filename = filename;
 	festate->options = options;
 	festate->cstate = cstate;
+	festate->anum_rowid = anum_rowid;
+	festate->lineno = 1;
 
 	node->fdw_state = (void *) festate;
 }
@@ -639,7 +758,16 @@ fileIterateForeignScan(ForeignScanState *node)
 						 slot->tts_values, slot->tts_isnull,
 						 NULL);
 	if (found)
+	{
+		if (festate->anum_rowid != InvalidAttrNumber)
+		{
+			slot->tts_values[festate->anum_rowid - 1]
+				= Int32GetDatum(festate->lineno);
+			slot->tts_isnull[festate->anum_rowid - 1] = false;
+		}
 		ExecStoreVirtualTuple(slot);
+		festate->lineno++;
+	}
 
 	/* Remove error callback. */
 	error_context_stack = errcontext.previous;
@@ -662,6 +790,7 @@ fileReScanForeignScan(ForeignScanState *node)
 									festate->filename,
 									NIL,
 									festate->options);
+	festate->lineno = 1;
 }
 
 /*
@@ -717,6 +846,44 @@ fileAnalyzeForeignTable(Relation relation,
 	return true;
 }
 
+static void
+fileBeginForeignModify(CmdType operation,
+					   ResultRelInfo *resultRelInfo,
+					   EState *estate,
+					   Plan *subplan,
+					   int eflags)
+{
+	elog(INFO, "fdw_file: BeginForeignModify method");
+}
+
+static int
+fileExecForeignInsert(ResultRelInfo *resultRelInfo, HeapTuple tuple)
+{
+	elog(INFO, "fdw_file: INSERT");
+	return 1;
+}
+
+static int
+fileExecForeignDelete(ResultRelInfo *resultRelInfo, Datum rowid)
+{
+	elog(INFO, "fdw_file: DELETE (lineno = %u)", DatumGetInt32(rowid));
+	return 1;
+}
+
+static int
+fileExecForeignUpdate(ResultRelInfo *resultRelInfo,
+					  Datum rowid, HeapTuple tuple)
+{
+	elog(INFO, "fdw_file: UPDATE (lineno = %u)", DatumGetInt32(rowid));
+	return 1;
+}
+
+static void
+fileEndForeignModify(ResultRelInfo *resultRelInfo)
+{
+	elog(INFO, "fdw_file: EndForeignModify method");
+}
+
 /*
  * check_selective_binary_conversion
  *
@@ -789,8 +956,8 @@ check_selective_binary_conversion(RelOptInfo *baserel,
 			break;
 		}
 
-		/* Ignore system attributes. */
-		if (attnum < 0)
+		/* Ignore system or pseudo attributes. */
+		if (attnum < 0 || attnum > RelationGetNumberOfAttributes(rel))
 			continue;
 
 		/* Get user attributes. */
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 4d3ed9c..c67c051 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -2132,9 +2132,10 @@ ExecASDeleteTriggers(EState *estate, ResultRelInfo *relinfo)
 bool
 ExecBRDeleteTriggers(EState *estate, EPQState *epqstate,
 					 ResultRelInfo *relinfo,
-					 ItemPointer tupleid)
+					 Datum rowid)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
+	ItemPointer tupleid = (ItemPointer)DatumGetPointer(rowid);
 	bool		result = true;
 	TriggerData LocTriggerData;
 	HeapTuple	trigtuple;
@@ -2190,9 +2191,10 @@ ExecBRDeleteTriggers(EState *estate, EPQState *epqstate,
 
 void
 ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
-					 ItemPointer tupleid)
+					 Datum rowid)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
+	ItemPointer	tupleid = (ItemPointer)DatumGetPointer(rowid);
 
 	if (trigdesc && trigdesc->trig_delete_after_row)
 	{
@@ -2317,11 +2319,12 @@ ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
 TupleTableSlot *
 ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
 					 ResultRelInfo *relinfo,
-					 ItemPointer tupleid, TupleTableSlot *slot)
+					 Datum rowid, TupleTableSlot *slot)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 	HeapTuple	slottuple = ExecMaterializeSlot(slot);
 	HeapTuple	newtuple = slottuple;
+	ItemPointer	tupleid = (ItemPointer)DatumGetPointer(rowid);
 	TriggerData LocTriggerData;
 	HeapTuple	trigtuple;
 	HeapTuple	oldtuple;
@@ -2414,10 +2417,11 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
 
 void
 ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
-					 ItemPointer tupleid, HeapTuple newtuple,
+					 Datum rowid, HeapTuple newtuple,
 					 List *recheckIndexes)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
+	ItemPointer	tupleid = (ItemPointer)DatumGetPointer(rowid);
 
 	if (trigdesc && trigdesc->trig_update_after_row)
 	{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index d966be5..c701fe7 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"
@@ -934,6 +935,7 @@ void
 CheckValidResultRel(Relation resultRel, CmdType operation)
 {
 	TriggerDesc *trigDesc = resultRel->trigdesc;
+	FdwRoutine	*fdwroutine;
 
 	switch (resultRel->rd_rel->relkind)
 	{
@@ -985,10 +987,34 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
 			}
 			break;
 		case RELKIND_FOREIGN_TABLE:
-			ereport(ERROR,
-					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("cannot change foreign table \"%s\"",
-							RelationGetRelationName(resultRel))));
+			fdwroutine = GetFdwRoutineByRelId(RelationGetRelid(resultRel));
+			switch (operation)
+			{
+				case CMD_INSERT:
+					if (!fdwroutine || !fdwroutine->ExecForeignInsert)
+						ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("cannot insert into foreign table \"%s\"",
+									RelationGetRelationName(resultRel))));
+					break;
+				case CMD_UPDATE:
+					if (!fdwroutine || !fdwroutine->ExecForeignUpdate)
+						ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("cannot update foreign table \"%s\"",
+									RelationGetRelationName(resultRel))));
+					break;
+				case CMD_DELETE:
+					if (!fdwroutine || !fdwroutine->ExecForeignDelete)
+						ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("cannot delete from foreign table \"%s\"",
+									RelationGetRelationName(resultRel))));
+					break;
+				default:
+					elog(ERROR, "unrecognized CmdType: %d", (int) operation);
+					break;
+			}
 			break;
 		default:
 			ereport(ERROR,
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 9204859..1ee626f 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -25,6 +25,7 @@
 #include "executor/executor.h"
 #include "executor/nodeForeignscan.h"
 #include "foreign/fdwapi.h"
+#include "nodes/nodeFuncs.h"
 #include "utils/rel.h"
 
 static TupleTableSlot *ForeignNext(ForeignScanState *node);
@@ -93,6 +94,133 @@ ExecForeignScan(ForeignScanState *node)
 					(ExecScanRecheckMtd) ForeignRecheck);
 }
 
+/*
+ * pseudo_column_walker
+ *
+ * helper routine of GetPseudoTupleDesc. It pulls Var nodes that reference
+ * pseudo columns from targetlis of the relation
+ */
+typedef struct
+{
+	Relation	relation;
+	Index		varno;
+	List	   *pcolumns;
+	AttrNumber	max_attno;
+} pseudo_column_walker_context;
+
+static bool
+pseudo_column_walker(Node *node, pseudo_column_walker_context *context)
+{
+	if (node == NULL)
+		return false;
+	if (IsA(node, Var))
+	{
+		Var		   *var = (Var *) node;
+		ListCell   *cell;
+
+		if (var->varno == context->varno && var->varlevelsup == 0 &&
+			var->varattno > RelationGetNumberOfAttributes(context->relation))
+		{
+			foreach (cell, context->pcolumns)
+			{
+				Var	   *temp = lfirst(cell);
+
+				if (temp->varattno == var->varattno)
+				{
+					if (!equal(var, temp))
+						elog(ERROR, "asymmetric pseudo column appeared");
+					break;
+				}
+			}
+			if (!cell)
+			{
+				context->pcolumns = lappend(context->pcolumns, var);
+				if (var->varattno > context->max_attno)
+					context->max_attno = var->varattno;
+			}
+		}
+		return false;
+	}
+
+	/* Should not find an unplanned subquery */
+	Assert(!IsA(node, Query));
+
+	return expression_tree_walker(node, pseudo_column_walker,
+								  (void *)context);
+}
+
+/*
+ * GetPseudoTupleDesc
+ *
+ * It generates TupleDesc structure including pseudo-columns if required.
+ */
+static TupleDesc
+GetPseudoTupleDesc(ForeignScan *node, Relation relation)
+{
+	pseudo_column_walker_context context;
+	List	   *target_list = node->scan.plan.targetlist;
+	TupleDesc	tupdesc;
+	AttrNumber	attno;
+	ListCell   *cell;
+	ListCell   *prev;
+	bool		hasoid;
+
+	context.relation = relation;
+	context.varno = node->scan.scanrelid;
+	context.pcolumns = NIL;
+	context.max_attno = -1;
+
+	pseudo_column_walker((Node *)target_list, (void *)&context);
+	Assert(context.max_attno > RelationGetNumberOfAttributes(relation));
+
+	hasoid = RelationGetForm(relation)->relhasoids;
+	tupdesc = CreateTemplateTupleDesc(context.max_attno, hasoid);
+
+	for (attno = 1; attno <= context.max_attno; attno++)
+	{
+		/* case of regular columns */
+		if (attno <= RelationGetNumberOfAttributes(relation))
+		{
+			memcpy(tupdesc->attrs[attno - 1],
+				   RelationGetDescr(relation)->attrs[attno - 1],
+				   ATTRIBUTE_FIXED_PART_SIZE);
+			continue;
+		}
+
+		/* case of pseudo columns */
+		prev = NULL;
+		foreach (cell, context.pcolumns)
+		{
+			Var	   *var = lfirst(cell);
+
+			if (var->varattno == attno)
+			{
+				char		namebuf[NAMEDATALEN];
+
+				snprintf(namebuf, sizeof(namebuf),
+						 "pseudo_column_%d", attno);
+
+				TupleDescInitEntry(tupdesc,
+								   attno,
+								   namebuf,
+								   var->vartype,
+								   var->vartypmod,
+								   0);
+				TupleDescInitEntryCollation(tupdesc,
+											attno,
+											var->varcollid);
+				context.pcolumns
+					= list_delete_cell(context.pcolumns, cell, prev);
+				break;
+			}
+			prev = cell;
+		}
+		if (!cell)
+			elog(ERROR, "pseudo column %d of %s not in target list",
+				 attno, RelationGetRelationName(relation));
+	}
+	return tupdesc;
+}
 
 /* ----------------------------------------------------------------
  *		ExecInitForeignScan
@@ -103,6 +231,7 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
 {
 	ForeignScanState *scanstate;
 	Relation	currentRelation;
+	TupleDesc	tupdesc;
 	FdwRoutine *fdwroutine;
 
 	/* check for unsupported flags */
@@ -149,7 +278,12 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
 	/*
 	 * get the scan type from the relation descriptor.
 	 */
-	ExecAssignScanType(&scanstate->ss, RelationGetDescr(currentRelation));
+	if (node->fsPseudoCol)
+		tupdesc = GetPseudoTupleDesc(node, currentRelation);
+	else
+		tupdesc = RelationGetDescr(currentRelation);
+
+	ExecAssignScanType(&scanstate->ss, tupdesc);
 
 	/*
 	 * Initialize result tuple type and projection info.
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 26a59d0..97a0d82 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"
@@ -170,6 +171,7 @@ ExecInsert(TupleTableSlot *slot,
 	Relation	resultRelationDesc;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
+	int			num_rows = 1;
 
 	/*
 	 * get the heap tuple out of the tuple table slot, making sure we have a
@@ -225,6 +227,14 @@ ExecInsert(TupleTableSlot *slot,
 
 		newId = InvalidOid;
 	}
+	else if (resultRelInfo->ri_fdwroutine)
+	{
+		FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+		num_rows = fdwroutine->ExecForeignInsert(resultRelInfo, tuple);
+
+		newId = InvalidOid;
+	}
 	else
 	{
 		/*
@@ -252,7 +262,7 @@ ExecInsert(TupleTableSlot *slot,
 
 	if (canSetTag)
 	{
-		(estate->es_processed)++;
+		(estate->es_processed) += num_rows;
 		estate->es_lastoid = newId;
 		setLastTid(&(tuple->t_self));
 	}
@@ -285,7 +295,7 @@ ExecInsert(TupleTableSlot *slot,
  * ----------------------------------------------------------------
  */
 static TupleTableSlot *
-ExecDelete(ItemPointer tupleid,
+ExecDelete(Datum rowid,
 		   HeapTupleHeader oldtuple,
 		   TupleTableSlot *planSlot,
 		   EPQState *epqstate,
@@ -297,6 +307,7 @@ ExecDelete(ItemPointer tupleid,
 	HTSU_Result result;
 	ItemPointerData update_ctid;
 	TransactionId update_xmax;
+	int			num_rows = 1;
 
 	/*
 	 * get information on the (current) result relation
@@ -311,7 +322,7 @@ ExecDelete(ItemPointer tupleid,
 		bool		dodelete;
 
 		dodelete = ExecBRDeleteTriggers(estate, epqstate, resultRelInfo,
-										tupleid);
+										rowid);
 
 		if (!dodelete)			/* "do nothing" */
 			return NULL;
@@ -335,8 +346,16 @@ ExecDelete(ItemPointer tupleid,
 		if (!dodelete)			/* "do nothing" */
 			return NULL;
 	}
+	else if (resultRelInfo->ri_fdwroutine)
+	{
+		FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+		num_rows = fdwroutine->ExecForeignDelete(resultRelInfo, rowid);
+	}
 	else
 	{
+		ItemPointer	tupleid = (ItemPointer) DatumGetPointer(rowid);
+
 		/*
 		 * delete the tuple
 		 *
@@ -401,10 +420,10 @@ ldelete:;
 	}
 
 	if (canSetTag)
-		(estate->es_processed)++;
+		(estate->es_processed) += num_rows;
 
 	/* AFTER ROW DELETE Triggers */
-	ExecARDeleteTriggers(estate, resultRelInfo, tupleid);
+	ExecARDeleteTriggers(estate, resultRelInfo, rowid);
 
 	/* Process RETURNING if present */
 	if (resultRelInfo->ri_projectReturning)
@@ -428,7 +447,8 @@ ldelete:;
 		}
 		else
 		{
-			deltuple.t_self = *tupleid;
+			ItemPointerCopy((ItemPointer)DatumGetPointer(rowid),
+							&deltuple.t_self);
 			if (!heap_fetch(resultRelationDesc, SnapshotAny,
 							&deltuple, &delbuffer, false, NULL))
 				elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
@@ -470,7 +490,7 @@ ldelete:;
  * ----------------------------------------------------------------
  */
 static TupleTableSlot *
-ExecUpdate(ItemPointer tupleid,
+ExecUpdate(Datum rowid,
 		   HeapTupleHeader oldtuple,
 		   TupleTableSlot *slot,
 		   TupleTableSlot *planSlot,
@@ -485,6 +505,7 @@ ExecUpdate(ItemPointer tupleid,
 	ItemPointerData update_ctid;
 	TransactionId update_xmax;
 	List	   *recheckIndexes = NIL;
+	int			num_rows = 1;
 
 	/*
 	 * abort the operation if not running transactions
@@ -509,7 +530,7 @@ ExecUpdate(ItemPointer tupleid,
 		resultRelInfo->ri_TrigDesc->trig_update_before_row)
 	{
 		slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
-									tupleid, slot);
+									rowid, slot);
 
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
@@ -539,8 +560,16 @@ ExecUpdate(ItemPointer tupleid,
 		/* trigger might have changed tuple */
 		tuple = ExecMaterializeSlot(slot);
 	}
+	else if (resultRelInfo->ri_fdwroutine)
+	{
+		FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+		num_rows = fdwroutine->ExecForeignUpdate(resultRelInfo, rowid, tuple);
+	}
 	else
 	{
+		ItemPointer	tupleid = (ItemPointer) DatumGetPointer(rowid);
+
 		/*
 		 * Check the constraints of the tuple
 		 *
@@ -630,10 +659,10 @@ lreplace:;
 	}
 
 	if (canSetTag)
-		(estate->es_processed)++;
+		(estate->es_processed) += num_rows;
 
 	/* AFTER ROW UPDATE Triggers */
-	ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple,
+	ExecARUpdateTriggers(estate, resultRelInfo, rowid, tuple,
 						 recheckIndexes);
 
 	list_free(recheckIndexes);
@@ -714,6 +743,7 @@ ExecModifyTable(ModifyTableState *node)
 	TupleTableSlot *planSlot;
 	ItemPointer tupleid = NULL;
 	ItemPointerData tuple_ctid;
+	Datum		rowid = 0;
 	HeapTupleHeader oldtuple = NULL;
 
 	/*
@@ -802,17 +832,19 @@ ExecModifyTable(ModifyTableState *node)
 		if (junkfilter != NULL)
 		{
 			/*
-			 * extract the 'ctid' or 'wholerow' junk attribute.
+			 * extract the 'ctid', 'rowid' or 'wholerow' junk attribute.
 			 */
 			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,
+												 junkfilter->jf_junkRowidNo,
 												 &isNull);
 					/* shouldn't ever get a null result... */
 					if (isNull)
@@ -820,13 +852,33 @@ ExecModifyTable(ModifyTableState *node)
 
 					tupleid = (ItemPointer) DatumGetPointer(datum);
 					tuple_ctid = *tupleid;		/* be sure we don't free
-												 * ctid!! */
-					tupleid = &tuple_ctid;
+												 * ctid ! */
+					rowid = PointerGetDatum(&tuple_ctid);
+				}
+				else if (relkind == RELKIND_FOREIGN_TABLE)
+				{
+					datum = ExecGetJunkAttribute(slot,
+												 junkfilter->jf_junkRowidNo,
+												 &isNull);
+					/* shouldn't ever get a null result... */
+					if (isNull)
+						elog(ERROR, "rowid is NULL");
+
+					rowid = datum;
+
+					datum = ExecGetJunkAttribute(slot,
+												 junkfilter->jf_junkRecordNo,
+												 &isNull);
+					/* shouldn't ever get a null result... */
+					if (isNull)
+						elog(ERROR, "wholerow is NULL");
+
+					oldtuple = DatumGetHeapTupleHeader(datum);
 				}
 				else
 				{
 					datum = ExecGetJunkAttribute(slot,
-												 junkfilter->jf_junkAttNo,
+												 junkfilter->jf_junkRecordNo,
 												 &isNull);
 					/* shouldn't ever get a null result... */
 					if (isNull)
@@ -849,11 +901,11 @@ ExecModifyTable(ModifyTableState *node)
 				slot = ExecInsert(slot, planSlot, estate, node->canSetTag);
 				break;
 			case CMD_UPDATE:
-				slot = ExecUpdate(tupleid, oldtuple, slot, planSlot,
+				slot = ExecUpdate(rowid, oldtuple, slot, planSlot,
 								&node->mt_epqstate, estate, node->canSetTag);
 				break;
 			case CMD_DELETE:
-				slot = ExecDelete(tupleid, oldtuple, planSlot,
+				slot = ExecDelete(rowid, oldtuple, planSlot,
 								&node->mt_epqstate, estate, node->canSetTag);
 				break;
 			default:
@@ -965,6 +1017,22 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		estate->es_result_relation_info = resultRelInfo;
 		mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
 
+		/*
+		 * Also tells FDW extensions to init the plan for this result rel
+		 */
+		if (RelationGetForm(resultRelInfo->ri_RelationDesc)->relkind == RELKIND_FOREIGN_TABLE)
+		{
+			Oid		relid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+			FdwRoutine *fdwroutine = GetFdwRoutineByRelId(relid);
+
+			Assert(fdwroutine != NULL);
+			resultRelInfo->ri_fdwroutine = fdwroutine;
+			resultRelInfo->ri_fdw_state = NULL;
+
+			if (fdwroutine->BeginForeignModify)
+				fdwroutine->BeginForeignModify(operation, resultRelInfo,
+											   estate, subplan, eflags);
+		}
 		resultRelInfo++;
 		i++;
 	}
@@ -1106,6 +1174,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			for (i = 0; i < nplans; i++)
 			{
 				JunkFilter *j;
+				char		relkind =
+				    RelationGetForm(resultRelInfo->ri_RelationDesc)->relkind;
 
 				subplan = mtstate->mt_plans[i]->plan;
 				if (operation == CMD_INSERT || operation == CMD_UPDATE)
@@ -1119,16 +1189,27 @@ 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)
+					if (relkind == RELKIND_RELATION)
 					{
-						j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
-						if (!AttributeNumberIsValid(j->jf_junkAttNo))
+						j->jf_junkRowidNo = ExecFindJunkAttribute(j, "ctid");
+						if (!AttributeNumberIsValid(j->jf_junkRowidNo))
 							elog(ERROR, "could not find junk ctid column");
 					}
+					else if (relkind == RELKIND_FOREIGN_TABLE)
+					{
+						j->jf_junkRowidNo = ExecFindJunkAttribute(j, "rowid");
+						if (!AttributeNumberIsValid(j->jf_junkRowidNo))
+							elog(ERROR, "could not find junk rowid column");
+						j->jf_junkRecordNo
+							= ExecFindJunkAttribute(j, "record");
+						if (!AttributeNumberIsValid(j->jf_junkRecordNo))
+							elog(ERROR, "could not find junk wholerow column");
+					}
 					else
 					{
-						j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
-						if (!AttributeNumberIsValid(j->jf_junkAttNo))
+						j->jf_junkRecordNo
+							= ExecFindJunkAttribute(j, "wholerow");
+						if (!AttributeNumberIsValid(j->jf_junkRecordNo))
 							elog(ERROR, "could not find junk wholerow column");
 					}
 				}
@@ -1182,6 +1263,21 @@ ExecEndModifyTable(ModifyTableState *node)
 {
 	int			i;
 
+	/* Let the FDW shut dowm */
+	for (i=0; i < node->ps.state->es_num_result_relations; i++)
+	{
+		ResultRelInfo  *resultRelInfo
+			= &node->ps.state->es_result_relations[i];
+
+		if (resultRelInfo->ri_fdwroutine)
+		{
+			FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+			if (fdwroutine->EndForeignModify)
+				fdwroutine->EndForeignModify(resultRelInfo);
+		}
+	}
+
 	/*
 	 * Free the exprcontext
 	 */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index f34f704..3983e87 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -594,6 +594,7 @@ _copyForeignScan(const ForeignScan *from)
 	COPY_NODE_FIELD(fdw_exprs);
 	COPY_NODE_FIELD(fdw_private);
 	COPY_SCALAR_FIELD(fsSystemCol);
+	COPY_SCALAR_FIELD(fsPseudoCol);
 
 	return newnode;
 }
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 02a0f62..6f16ef0 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -562,6 +562,7 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
 	WRITE_NODE_FIELD(fdw_exprs);
 	WRITE_NODE_FIELD(fdw_private);
 	WRITE_BOOL_FIELD(fsSystemCol);
+	WRITE_BOOL_FIELD(fsPseudoCol);
 }
 
 static void
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 030f420..ff3b9ff 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -39,6 +39,7 @@
 #include "parser/parse_clause.h"
 #include "parser/parsetree.h"
 #include "utils/lsyscache.h"
+#include "utils/rel.h"
 
 
 static Plan *create_plan_recurse(PlannerInfo *root, Path *best_path);
@@ -1943,6 +1944,8 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 	RelOptInfo *rel = best_path->path.parent;
 	Index		scan_relid = rel->relid;
 	RangeTblEntry *rte;
+	Relation	relation;
+	AttrNumber	num_attrs;
 	int			i;
 
 	/* it should be a base rel... */
@@ -2001,6 +2004,22 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 		}
 	}
 
+	/*
+	 * Also, detect whether any pseudo columns are requested from rel.
+	 */
+	relation = heap_open(rte->relid, NoLock);
+	scan_plan->fsPseudoCol = false;
+	num_attrs = RelationGetNumberOfAttributes(relation);
+	for (i = num_attrs + 1; i <= rel->max_attr; i++)
+	{
+		if (!bms_is_empty(rel->attr_needed[i - rel->min_attr]))
+		{
+			scan_plan->fsPseudoCol = true;
+			break;
+		}
+	}
+	heap_close(relation, NoLock);
+
 	return scan_plan;
 }
 
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 9565e2d..740bb1e 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -82,7 +82,7 @@ static void check_hashjoinable(RestrictInfo *restrictinfo);
  * "other rel" RelOptInfos for the members of any appendrels we find here.)
  */
 void
-add_base_rels_to_query(PlannerInfo *root, Node *jtnode)
+add_base_rels_to_query(PlannerInfo *root, List *tlist, Node *jtnode)
 {
 	if (jtnode == NULL)
 		return;
@@ -90,7 +90,7 @@ add_base_rels_to_query(PlannerInfo *root, Node *jtnode)
 	{
 		int			varno = ((RangeTblRef *) jtnode)->rtindex;
 
-		(void) build_simple_rel(root, varno, RELOPT_BASEREL);
+		(void) build_simple_rel(root, varno, tlist, RELOPT_BASEREL);
 	}
 	else if (IsA(jtnode, FromExpr))
 	{
@@ -98,14 +98,14 @@ add_base_rels_to_query(PlannerInfo *root, Node *jtnode)
 		ListCell   *l;
 
 		foreach(l, f->fromlist)
-			add_base_rels_to_query(root, lfirst(l));
+			add_base_rels_to_query(root, tlist, lfirst(l));
 	}
 	else if (IsA(jtnode, JoinExpr))
 	{
 		JoinExpr   *j = (JoinExpr *) jtnode;
 
-		add_base_rels_to_query(root, j->larg);
-		add_base_rels_to_query(root, j->rarg);
+		add_base_rels_to_query(root, tlist, j->larg);
+		add_base_rels_to_query(root, tlist, j->rarg);
 	}
 	else
 		elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
index c2488a4..2d292dd 100644
--- a/src/backend/optimizer/plan/planmain.c
+++ b/src/backend/optimizer/plan/planmain.c
@@ -163,7 +163,7 @@ query_planner(PlannerInfo *root, List *tlist,
 	 * rangetable may contain RTEs for rels not actively part of the query,
 	 * for example views.  We don't want to make RelOptInfos for them.
 	 */
-	add_base_rels_to_query(root, (Node *) parse->jointree);
+	add_base_rels_to_query(root, tlist, (Node *) parse->jointree);
 
 	/*
 	 * Examine the targetlist and join tree, adding entries to baserel
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 385e646..7951762 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -3310,7 +3310,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 	setup_simple_rel_arrays(root);
 
 	/* Build RelOptInfo */
-	rel = build_simple_rel(root, 1, RELOPT_BASEREL);
+	rel = build_simple_rel(root, 1, NIL, RELOPT_BASEREL);
 
 	/* Locate IndexOptInfo for the target index */
 	indexInfo = NULL;
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 9e154e1..391721e 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -236,7 +236,8 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 		 * used for anything here, but it carries the subroot data structures
 		 * forward to setrefs.c processing.
 		 */
-		rel = build_simple_rel(root, rtr->rtindex, RELOPT_BASEREL);
+		rel = build_simple_rel(root, rtr->rtindex, refnames_tlist,
+							   RELOPT_BASEREL);
 
 		/* plan_params should not be in use in current query level */
 		Assert(root->plan_params == NIL);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 25f8785..3b25ab5 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -25,6 +25,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/heap.h"
+#include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/clauses.h"
@@ -80,7 +81,7 @@ static List *build_index_tlist(PlannerInfo *root, IndexOptInfo *index,
  */
 void
 get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
-				  RelOptInfo *rel)
+				  List *tlist, RelOptInfo *rel)
 {
 	Index		varno = rel->relid;
 	Relation	relation;
@@ -104,6 +105,26 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 	rel->max_attr = RelationGetNumberOfAttributes(relation);
 	rel->reltablespace = RelationGetForm(relation)->reltablespace;
 
+	/*
+	 * Adjust width of attr_needed slot in case when FDW extension wants
+	 * or needs to return pseudo-columns also, not only columns in its
+	 * table definition.
+	 * GetForeignRelInfo, an optional FDW handler, enables FDW extension
+	 * to save properties of pseudo-column on its private field.
+	 * When foreign-table is the target of UPDATE/DELETE, query-rewritter
+	 * injects "rowid" pseudo-column to track remote row to be modified,
+	 * so FDW has to track which varattno shall perform as "rowid".
+	 */
+	if (RelationGetForm(relation)->relkind == RELKIND_FOREIGN_TABLE)
+	{
+		FdwRoutine *fdwroutine = GetFdwRoutineByRelId(relationObjectId);
+
+		if (fdwroutine->GetForeignRelInfo)
+			fdwroutine->GetForeignRelInfo(root, rel,
+										  relationObjectId,
+										  inhparent, tlist);
+	}
+
 	Assert(rel->max_attr >= rel->min_attr);
 	rel->attr_needed = (Relids *)
 		palloc0((rel->max_attr - rel->min_attr + 1) * sizeof(Relids));
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index f724714..84b674c 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -80,7 +80,8 @@ setup_simple_rel_arrays(PlannerInfo *root)
  *	  Construct a new RelOptInfo for a base relation or 'other' relation.
  */
 RelOptInfo *
-build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
+build_simple_rel(PlannerInfo *root, int relid, List *tlist,
+				 RelOptKind reloptkind)
 {
 	RelOptInfo *rel;
 	RangeTblEntry *rte;
@@ -133,7 +134,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
 	{
 		case RTE_RELATION:
 			/* Table --- retrieve statistics from the system catalogs */
-			get_relation_info(root, rte->relid, rte->inh, rel);
+			get_relation_info(root, rte->relid, rte->inh, tlist, rel);
 			break;
 		case RTE_SUBQUERY:
 		case RTE_FUNCTION:
@@ -180,7 +181,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
 			if (appinfo->parent_relid != relid)
 				continue;
 
-			(void) build_simple_rel(root, appinfo->child_relid,
+			(void) build_simple_rel(root, appinfo->child_relid, tlist,
 									RELOPT_OTHER_MEMBER_REL);
 		}
 	}
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 8f75948..e78992b 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1163,7 +1163,10 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 					Relation target_relation)
 {
 	Var		   *var;
-	const char *attrname;
+	List	   *varList;
+	List	   *attNameList;
+	ListCell   *cell1;
+	ListCell   *cell2;
 	TargetEntry *tle;
 
 	if (target_relation->rd_rel->relkind == RELKIND_RELATION)
@@ -1177,8 +1180,35 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 					  -1,
 					  InvalidOid,
 					  0);
+		varList = list_make1(var);
+		attNameList = list_make1("ctid");
+	}
+	else if (target_relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+	{
+		/*
+		 * Emit Rowid so that executor can find the row to update or delete.
+		 */
+		var = makeVar(parsetree->resultRelation,
+					  RelationGetNumberOfAttributes(target_relation) + 1,
+					  INTERNALOID,
+					  -1,
+					  InvalidOid,
+					  0);
+		varList = list_make1(var);
 
-		attrname = "ctid";
+		/*
+		 * Emit generic record Var so that executor will have the "old" view
+		 * row to pass the RETURNING clause (or upcoming triggers).
+		 */
+		var = makeVar(parsetree->resultRelation,
+					  InvalidAttrNumber,
+					  RECORDOID,
+					  -1,
+					  InvalidOid,
+					  0);
+		varList = lappend(varList, var);
+
+		attNameList = list_make2("rowid", "record");
 	}
 	else
 	{
@@ -1190,16 +1220,21 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 							  parsetree->resultRelation,
 							  0,
 							  false);
-
-		attrname = "wholerow";
+		varList = list_make1(var);
+		attNameList = list_make1("wholerow");
 	}
 
-	tle = makeTargetEntry((Expr *) var,
-						  list_length(parsetree->targetList) + 1,
-						  pstrdup(attrname),
-						  true);
-
-	parsetree->targetList = lappend(parsetree->targetList, tle);
+	/*
+	 * Append them to targetList
+	 */
+	forboth (cell1, varList, cell2, attNameList)
+	{
+		tle = makeTargetEntry((Expr *)lfirst(cell1),
+							  list_length(parsetree->targetList) + 1,
+							  pstrdup((const char *)lfirst(cell2)),
+							  true);
+		parsetree->targetList = lappend(parsetree->targetList, tle);
+	}
 }
 
 
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index e790b9d..8370e05 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -147,10 +147,10 @@ extern void ExecASDeleteTriggers(EState *estate,
 extern bool ExecBRDeleteTriggers(EState *estate,
 					 EPQState *epqstate,
 					 ResultRelInfo *relinfo,
-					 ItemPointer tupleid);
+					 Datum rowid);
 extern void ExecARDeleteTriggers(EState *estate,
 					 ResultRelInfo *relinfo,
-					 ItemPointer tupleid);
+					 Datum rowid);
 extern bool ExecIRDeleteTriggers(EState *estate,
 					 ResultRelInfo *relinfo,
 					 HeapTuple trigtuple);
@@ -161,11 +161,11 @@ extern void ExecASUpdateTriggers(EState *estate,
 extern TupleTableSlot *ExecBRUpdateTriggers(EState *estate,
 					 EPQState *epqstate,
 					 ResultRelInfo *relinfo,
-					 ItemPointer tupleid,
+					 Datum rowid,
 					 TupleTableSlot *slot);
 extern void ExecARUpdateTriggers(EState *estate,
 					 ResultRelInfo *relinfo,
-					 ItemPointer tupleid,
+					 Datum rowid,
 					 HeapTuple newtuple,
 					 List *recheckIndexes);
 extern TupleTableSlot *ExecIRUpdateTriggers(EState *estate,
diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
index 721cd25..8b29532 100644
--- a/src/include/foreign/fdwapi.h
+++ b/src/include/foreign/fdwapi.h
@@ -22,6 +22,11 @@ struct ExplainState;
 /*
  * Callback function signatures --- see fdwhandler.sgml for more info.
  */
+typedef void (*GetForeignRelInfo_function) (PlannerInfo *root,
+											RelOptInfo *baserel,
+											Oid foreigntableid,
+											bool inhparent,
+											List *targetList);
 
 typedef void (*GetForeignRelSize_function) (PlannerInfo *root,
 														RelOptInfo *baserel,
@@ -59,6 +64,20 @@ typedef bool (*AnalyzeForeignTable_function) (Relation relation,
 												 AcquireSampleRowsFunc *func,
 													BlockNumber *totalpages);
 
+typedef void (*BeginForeignModify_function) (CmdType operation,
+											 ResultRelInfo *resultRelInfo,
+											 EState *estate,
+											 Plan *subplan,
+											 int eflags);
+typedef int	(*ExecForeignInsert_function) (ResultRelInfo *resultRelInfo,
+										   HeapTuple tuple);
+typedef int	(*ExecForeignDelete_function) (ResultRelInfo *resultRelInfo,
+										   Datum rowid);
+typedef int	(*ExecForeignUpdate_function) (ResultRelInfo *resultRelInfo,
+										   Datum rowid,
+										   HeapTuple tuple);
+typedef void (*EndForeignModify_function) (ResultRelInfo *resultRelInfo);
+
 /*
  * FdwRoutine is the struct returned by a foreign-data wrapper's handler
  * function.  It provides pointers to the callback functions needed by the
@@ -90,6 +109,12 @@ typedef struct FdwRoutine
 	 * not provided.
 	 */
 	AnalyzeForeignTable_function AnalyzeForeignTable;
+	GetForeignRelInfo_function GetForeignRelInfo;
+	BeginForeignModify_function	BeginForeignModify;
+	ExecForeignInsert_function ExecForeignInsert;
+	ExecForeignDelete_function ExecForeignDelete;
+	ExecForeignUpdate_function ExecForeignUpdate;
+	EndForeignModify_function EndForeignModify;
 } FdwRoutine;
 
 
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index fec07b8..d8fa299 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -268,9 +268,11 @@ typedef struct ProjectionInfo
  *						attribute numbers of the "original" tuple and the
  *						attribute numbers of the "clean" tuple.
  *	  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
+ *	  jf_junkRowidNo:	not used by junkfilter code.  Can be used by caller
+ *						to remember the attno of to track a particular tuple
+ *						being updated or deleted.
  *						(execMain.c stores the "ctid" attno here).
+ *	  jf_junkRecordNo:	Also, the attno of whole-row reference.
  * ----------------
  */
 typedef struct JunkFilter
@@ -280,7 +282,8 @@ typedef struct JunkFilter
 	TupleDesc	jf_cleanTupType;
 	AttrNumber *jf_cleanMap;
 	TupleTableSlot *jf_resultSlot;
-	AttrNumber	jf_junkAttNo;
+	AttrNumber	jf_junkRowidNo;
+	AttrNumber	jf_junkRecordNo;
 } JunkFilter;
 
 /* ----------------
@@ -303,6 +306,8 @@ typedef struct JunkFilter
  *		ConstraintExprs			array of constraint-checking expr states
  *		junkFilter				for removing junk attributes from tuples
  *		projectReturning		for computing a RETURNING list
+ *		fdwroutine				FDW callbacks if foreign table
+ *		fdw_state				opaque state of FDW module, or NULL
  * ----------------
  */
 typedef struct ResultRelInfo
@@ -320,6 +325,8 @@ typedef struct ResultRelInfo
 	List	  **ri_ConstraintExprs;
 	JunkFilter *ri_junkFilter;
 	ProjectionInfo *ri_projectReturning;
+	struct FdwRoutine  *ri_fdwroutine;
+	void	   *ri_fdw_state;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index fb9a863..a724863 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -478,6 +478,7 @@ typedef struct ForeignScan
 	List	   *fdw_exprs;		/* expressions that FDW may evaluate */
 	List	   *fdw_private;	/* private data for FDW */
 	bool		fsSystemCol;	/* true if any "system column" is needed */
+	bool		fsPseudoCol;	/* true if any "pseudo column" is needed */
 } ForeignScan;
 
 
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index de889fb..adfc93d 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -133,7 +133,7 @@ extern Path *reparameterize_path(PlannerInfo *root, Path *path,
  */
 extern void setup_simple_rel_arrays(PlannerInfo *root);
 extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid,
-				 RelOptKind reloptkind);
+			   List *tlist, RelOptKind reloptkind);
 extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid);
 extern RelOptInfo *find_join_rel(PlannerInfo *root, Relids relids);
 extern RelOptInfo *build_join_rel(PlannerInfo *root,
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index e0d04db..c5fce41 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -26,7 +26,7 @@ extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook;
 
 
 extern void get_relation_info(PlannerInfo *root, Oid relationObjectId,
-				  bool inhparent, RelOptInfo *rel);
+				  bool inhparent, List *tlist, RelOptInfo *rel);
 
 extern void estimate_rel_size(Relation rel, int32 *attr_widths,
 				  BlockNumber *pages, double *tuples, double *allvisfrac);
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index c395d42..d8bc7da 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -90,7 +90,8 @@ extern bool is_projection_capable_plan(Plan *plan);
 extern int	from_collapse_limit;
 extern int	join_collapse_limit;
 
-extern void add_base_rels_to_query(PlannerInfo *root, Node *jtnode);
+extern void add_base_rels_to_query(PlannerInfo *root, List *tlist,
+								   Node *jtnode);
 extern void build_base_rel_tlists(PlannerInfo *root, List *final_tlist);
 extern void add_vars_to_targetlist(PlannerInfo *root, List *vars,
 					   Relids where_needed, bool create_new_ph);
#25Alexander Korotkov
aekorotkov@gmail.com
In reply to: Kohei KaiGai (#24)
Re: [v9.3] writable foreign tables

On Mon, Sep 24, 2012 at 12:49 PM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

2012/9/23 Kohei KaiGai <kaigai@kaigai.gr.jp>:

2012/8/29 Kohei KaiGai <kaigai@kaigai.gr.jp>:

2012/8/28 Kohei KaiGai <kaigai@kaigai.gr.jp>:

2012/8/28 Tom Lane <tgl@sss.pgh.pa.us>:

Kohei KaiGai <kaigai@kaigai.gr.jp> writes:

Would it be too invasive to introduce a new pointer in

TupleTableSlot

that is NULL for anything but virtual tuples from foreign tables?

I'm not certain whether the duration of TupleTableSlot is enough to
carry a private datum between scan and modify stage.

It's not.

Is it possible to utilize ctid field to move a private pointer?

UPDATEs and DELETEs do not rely on the ctid field of tuples to carry

the

TID from scan to modify --- in fact, most of the time what the modify
step is going to get is a "virtual" TupleTableSlot that hasn't even
*got* a physical CTID field.

Instead, the planner arranges for the TID to be carried up as an
explicit resjunk column named ctid. (Currently this is done in
rewriteTargetListUD(), but see also preptlist.c which does some

related

things for SELECT FOR UPDATE.)

I'm inclined to think that what we need here is for FDWs to be able to
modify the details of that behavior, at least to the extent of being
able to specify a different data type than TID for the row
identification column.

Hmm. It seems to me a straight-forward solution rather than ab-use
of ctid system column. Probably, cstring data type is more suitable
to carry a private datum between scan and modify stage.

One problem I noticed is how FDW driver returns an extra field that
is in neither system nor regular column.
Number of columns and its data type are defined with TupleDesc of
the target foreign-table, so we also need a feature to extend it on
run-time. For example, FDW driver may have to be able to extend
a "virtual" column with cstring data type, even though the target
foreign table does not have such a column.

I tried to investigate the related routines.

TupleDesc of TupleTableSlot associated with ForeignScanState
is initialized at ExecInitForeignScan as literal.
ExecAssignScanType assigns TupleDesc of the target foreign-
table on tts_tupleDescriptor, "as-is".
It is the reason why IterateForeignScan cannot return a private
datum except for the columns being declared as regular ones.

The attached patch improved its design according to the upthread
discussion. It now got away from ab-use of "ctid" field, and adopts
a concept of pseudo-column to hold row-id with opaque data type
instead.

Pseudo-column is Var reference towards attribute-number larger
than number of attributes on the target relation; thus, it is not
a substantial object. It is normally unavailable to reference such
a larger attribute number because TupleDesc of each ScanState
associated with a particular relation is initialized at ExecInitNode.

The patched ExecInitForeignScan was extended to generate its
own TupleDesc including pseudo-column definitions on the fly,
instead of relation's one, when scan-plan of foreign-table requires
to have pseudo-columns.

Right now, the only possible pseudo-column is "rowid" being
injected at rewriteTargetListUD(). It has no data format
restriction like "ctid" because of VOID data type.
FDW extension can set an appropriate value on the "rowid"
field in addition to contents of regular columns at
IterateForeignScan method, to track which remote row should
be updated or deleted.

Another possible usage of this pseudo-column is push-down
of target-list including complex calculation. It may enable to
move complex mathematical formula into remote devices
(such as GPU device?) instead of just a reference of Var node.

This patch adds a new interface: GetForeignRelInfo being invoked
from get_relation_info() to adjust width of RelOptInfo->attr_needed
according to the target-list which may contain "rowid" pseudo-column.
Some FDW extension may use this interface to push-down a part of
target list into remote side, even though I didn't implement this
feature on file_fdw.

RelOptInfo->max_attr is a good marker whether the plan shall have
pseudo-column reference. Then, ExecInitForeignScan determines
whether it should generate a TupleDesc, or not.

The "rowid" is fetched using ExecGetJunkAttribute as we are currently
doing on regular tables using "ctid", then it shall be delivered to
ExecUpdate or ExecDelete. We can never expect the fist argument of
them now, so "ItemPointer tupleid" redefined to "Datum rowid", and
argument of BR-trigger routines redefined also.

[kaigai@iwashi sepgsql]$ cat ~/testfile.csv
10 aaa
11 bbb
12 ccc
13 ddd
14 eee
15 fff
[kaigai@iwashi sepgsql]$ psql postgres
psql (9.3devel)
Type "help" for help.

postgres=# UPDATE ftbl SET b = md5(b) WHERE a > 12 RETURNING *;
INFO: ftbl is the target relation of UPDATE
INFO: fdw_file: BeginForeignModify method
INFO: fdw_file: UPDATE (lineno = 4)
INFO: fdw_file: UPDATE (lineno = 5)
INFO: fdw_file: UPDATE (lineno = 6)
INFO: fdw_file: EndForeignModify method
a | b
----+----------------------------------
13 | 77963b7a931377ad4ab5ad6a9cd718aa
14 | d2f2297d6e829cd3493aa7de4416a18f
15 | 343d9040a671c45832ee5381860e2996
(3 rows)

UPDATE 3
postgres=# DELETE FROM ftbl WHERE a % 2 = 1 RETURNING *;
INFO: ftbl is the target relation of DELETE
INFO: fdw_file: BeginForeignModify method
INFO: fdw_file: DELETE (lineno = 2)
INFO: fdw_file: DELETE (lineno = 4)
INFO: fdw_file: DELETE (lineno = 6)
INFO: fdw_file: EndForeignModify method
a | b
----+-----
11 | bbb
13 | ddd
15 | fff
(3 rows)

DELETE 3

In addition, there is a small improvement. ExecForeignInsert,
ExecForeignUpdate and ExecForeignDelete get being able
to return number of processed rows; that allows to push-down
whole the statement into remote-side, if it is enough simple
(e.g, delete statement without any condition).

Even though it does not make matter right now, pseudo-columns
should be adjusted when foreign-table is referenced with table
inheritance feature, because an attribute number being enough
large in parent table is not enough large in child table.
We need to fix up them until foreign table feature got inheritance
capability.

I didn't update the documentation stuff because I positioned
the state of this patch as proof-of-concept now. Please note that.

A tiny bit of this patch was updated. I noticed INTERNAL data type
is more suitable to move a private datum from scan-stage to
modify-stage, because its type-length is declared as SIZEOF_POINTER.

Also, I fixed up some obvious compiler warnings.

I've read previous discussion about this patch. It's generally concentrated
on the question how to identify foreign table row? Your last patch
introduce "rowid" pseudo-column for foreign table row identification. My
notes are following:
1) AFAICS your patch are designed to support arbitrary number of
pseudo-columns while only one is currently used. Do you see more use cases
of pseudo-columns?
2) You wrote that FDW can support or don't support write depending on
having corresponding functions. However it's likely some tables of same FDW
could be writable while another are not. I think we should have some
mechanism for FDW telling whether particular table is writable.
3) I have another point about identification of foreign rows which I didn't
meet in previous discussion. What if we restrict writable FDW table to have
set of column which are unique identifier of a row. Many replication
systems have this restriction: relicated tables should have a unique key.
In case of text or csv file I don't see why making line number column user
visible is bad.

------
With best regards,
Alexander Korotkov.

#26Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Alexander Korotkov (#25)
Re: [v9.3] writable foreign tables

2012/11/2 Alexander Korotkov <aekorotkov@gmail.com>:

On Mon, Sep 24, 2012 at 12:49 PM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

2012/9/23 Kohei KaiGai <kaigai@kaigai.gr.jp>:

2012/8/29 Kohei KaiGai <kaigai@kaigai.gr.jp>:

2012/8/28 Kohei KaiGai <kaigai@kaigai.gr.jp>:

2012/8/28 Tom Lane <tgl@sss.pgh.pa.us>:

Kohei KaiGai <kaigai@kaigai.gr.jp> writes:

Would it be too invasive to introduce a new pointer in
TupleTableSlot
that is NULL for anything but virtual tuples from foreign tables?

I'm not certain whether the duration of TupleTableSlot is enough to
carry a private datum between scan and modify stage.

It's not.

Is it possible to utilize ctid field to move a private pointer?

UPDATEs and DELETEs do not rely on the ctid field of tuples to carry
the
TID from scan to modify --- in fact, most of the time what the modify
step is going to get is a "virtual" TupleTableSlot that hasn't even
*got* a physical CTID field.

Instead, the planner arranges for the TID to be carried up as an
explicit resjunk column named ctid. (Currently this is done in
rewriteTargetListUD(), but see also preptlist.c which does some
related
things for SELECT FOR UPDATE.)

I'm inclined to think that what we need here is for FDWs to be able
to
modify the details of that behavior, at least to the extent of being
able to specify a different data type than TID for the row
identification column.

Hmm. It seems to me a straight-forward solution rather than ab-use
of ctid system column. Probably, cstring data type is more suitable
to carry a private datum between scan and modify stage.

One problem I noticed is how FDW driver returns an extra field that
is in neither system nor regular column.
Number of columns and its data type are defined with TupleDesc of
the target foreign-table, so we also need a feature to extend it on
run-time. For example, FDW driver may have to be able to extend
a "virtual" column with cstring data type, even though the target
foreign table does not have such a column.

I tried to investigate the related routines.

TupleDesc of TupleTableSlot associated with ForeignScanState
is initialized at ExecInitForeignScan as literal.
ExecAssignScanType assigns TupleDesc of the target foreign-
table on tts_tupleDescriptor, "as-is".
It is the reason why IterateForeignScan cannot return a private
datum except for the columns being declared as regular ones.

The attached patch improved its design according to the upthread
discussion. It now got away from ab-use of "ctid" field, and adopts
a concept of pseudo-column to hold row-id with opaque data type
instead.

Pseudo-column is Var reference towards attribute-number larger
than number of attributes on the target relation; thus, it is not
a substantial object. It is normally unavailable to reference such
a larger attribute number because TupleDesc of each ScanState
associated with a particular relation is initialized at ExecInitNode.

The patched ExecInitForeignScan was extended to generate its
own TupleDesc including pseudo-column definitions on the fly,
instead of relation's one, when scan-plan of foreign-table requires
to have pseudo-columns.

Right now, the only possible pseudo-column is "rowid" being
injected at rewriteTargetListUD(). It has no data format
restriction like "ctid" because of VOID data type.
FDW extension can set an appropriate value on the "rowid"
field in addition to contents of regular columns at
IterateForeignScan method, to track which remote row should
be updated or deleted.

Another possible usage of this pseudo-column is push-down
of target-list including complex calculation. It may enable to
move complex mathematical formula into remote devices
(such as GPU device?) instead of just a reference of Var node.

This patch adds a new interface: GetForeignRelInfo being invoked
from get_relation_info() to adjust width of RelOptInfo->attr_needed
according to the target-list which may contain "rowid" pseudo-column.
Some FDW extension may use this interface to push-down a part of
target list into remote side, even though I didn't implement this
feature on file_fdw.

RelOptInfo->max_attr is a good marker whether the plan shall have
pseudo-column reference. Then, ExecInitForeignScan determines
whether it should generate a TupleDesc, or not.

The "rowid" is fetched using ExecGetJunkAttribute as we are currently
doing on regular tables using "ctid", then it shall be delivered to
ExecUpdate or ExecDelete. We can never expect the fist argument of
them now, so "ItemPointer tupleid" redefined to "Datum rowid", and
argument of BR-trigger routines redefined also.

[kaigai@iwashi sepgsql]$ cat ~/testfile.csv
10 aaa
11 bbb
12 ccc
13 ddd
14 eee
15 fff
[kaigai@iwashi sepgsql]$ psql postgres
psql (9.3devel)
Type "help" for help.

postgres=# UPDATE ftbl SET b = md5(b) WHERE a > 12 RETURNING *;
INFO: ftbl is the target relation of UPDATE
INFO: fdw_file: BeginForeignModify method
INFO: fdw_file: UPDATE (lineno = 4)
INFO: fdw_file: UPDATE (lineno = 5)
INFO: fdw_file: UPDATE (lineno = 6)
INFO: fdw_file: EndForeignModify method
a | b
----+----------------------------------
13 | 77963b7a931377ad4ab5ad6a9cd718aa
14 | d2f2297d6e829cd3493aa7de4416a18f
15 | 343d9040a671c45832ee5381860e2996
(3 rows)

UPDATE 3
postgres=# DELETE FROM ftbl WHERE a % 2 = 1 RETURNING *;
INFO: ftbl is the target relation of DELETE
INFO: fdw_file: BeginForeignModify method
INFO: fdw_file: DELETE (lineno = 2)
INFO: fdw_file: DELETE (lineno = 4)
INFO: fdw_file: DELETE (lineno = 6)
INFO: fdw_file: EndForeignModify method
a | b
----+-----
11 | bbb
13 | ddd
15 | fff
(3 rows)

DELETE 3

In addition, there is a small improvement. ExecForeignInsert,
ExecForeignUpdate and ExecForeignDelete get being able
to return number of processed rows; that allows to push-down
whole the statement into remote-side, if it is enough simple
(e.g, delete statement without any condition).

Even though it does not make matter right now, pseudo-columns
should be adjusted when foreign-table is referenced with table
inheritance feature, because an attribute number being enough
large in parent table is not enough large in child table.
We need to fix up them until foreign table feature got inheritance
capability.

I didn't update the documentation stuff because I positioned
the state of this patch as proof-of-concept now. Please note that.

A tiny bit of this patch was updated. I noticed INTERNAL data type
is more suitable to move a private datum from scan-stage to
modify-stage, because its type-length is declared as SIZEOF_POINTER.

Also, I fixed up some obvious compiler warnings.

Thanks for your comments.

I've read previous discussion about this patch. It's generally concentrated
on the question how to identify foreign table row? Your last patch introduce
"rowid" pseudo-column for foreign table row identification. My notes are
following:
1) AFAICS your patch are designed to support arbitrary number of
pseudo-columns while only one is currently used. Do you see more use cases
of pseudo-columns?

Good question. Please see the p.39 of my slide at PGconf.EU 2012 (page of
"TargetList push-down")
(*) http://wiki.postgresql.org/wiki/PostgreSQL_Conference_Europe_Talks_2012

In case when targetList contains complex calculation such as mathematical
operation that takes long CPU cycles, this feature will allows to push down
burden of this calculation into foreign computing resource, not only
implementation
to move identifier of modified rows.
If we can move this calculation into remote RDBMS, all the local pgsql needs
to do is just reference a result value in spite of complex calculation.

2) You wrote that FDW can support or don't support write depending on having
corresponding functions. However it's likely some tables of same FDW could
be writable while another are not. I think we should have some mechanism for
FDW telling whether particular table is writable.

Hmm. It might be a thing to be considered. My preference is, it should
be handled
at callbacks at planner stage. FDW can raise an error according to individual
foreign table configuration, such as existence of primary key and so on.

The resultRelation of Query can tell whether the scan plan is for
modification of
the table, or not.

3) I have another point about identification of foreign rows which I didn't
meet in previous discussion. What if we restrict writable FDW table to have
set of column which are unique identifier of a row. Many replication systems
have this restriction: relicated tables should have a unique key. In case of
text or csv file I don't see why making line number column user visible is
bad.

The file_fdw portion of this patch is just a proof-of-concept, so I don't think
it is commitable enhancement right now. Thus, here is no special reason
why we don't expose the pseudo line number column for users, however,
it it not a fundamental essence of this patch.

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

#27Albe Laurenz
laurenz.albe@wien.gv.at
In reply to: Alexander Korotkov (#25)
Re: [v9.3] writable foreign tables

Alexander Korotkov wrote:

2) You wrote that FDW can support or don't support write depending on

having corresponding functions.

However it's likely some tables of same FDW could be writable while

another are not. I think we should

have some mechanism for FDW telling whether particular table is

writable.

I think that this would best be handled by a table option,
if necessary.
That allows maximum flexibility for the design of the FDW.
In many cases it might be enough if the foreign data source
raises an error on a write request.

Yours,
Laurenz Albe

#28Atri Sharma
atri.jiit@gmail.com
In reply to: Albe Laurenz (#27)
Re: [v9.3] writable foreign tables

On 08-Nov-2012, at 13:35, "Albe Laurenz" <laurenz.albe@wien.gv.at> wrote:

Alexander Korotkov wrote:

2) You wrote that FDW can support or don't support write depending on

having corresponding functions.

However it's likely some tables of same FDW could be writable while

another are not. I think we should

have some mechanism for FDW telling whether particular table is

writable.

I think that this would best be handled by a table option,
if necessary.
That allows maximum flexibility for the design of the FDW.
In many cases it might be enough if the foreign data source
raises an error on a write request.

Yours,
Laurenz Albe

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

+1

I agree, we should have a system where if the foreign data source raises an error on write, FDW can raise corresponding error on PostgreSQL side.exposing this as a table option is IMHO a bit risky, and the user may not know whether the foreign data source will accept writes or not.

Atri

#29Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Atri Sharma (#28)
1 attachment(s)
Re: [v9.3] writable foreign tables

The attached patch is just a refreshed version for clean applying to
the latest tree.

As previous version doing, it makes pseudo enhancement on file_fdw
to print something about the supplied tuple on INSERT, UPDATE and
DELETE statement.
Here is one other idea. My GPU acceleration module (PG-Strom)
implements column-oriented data store underlying foreign table.
It might make sense to cut out this portion for proof-of-concept of
writable foreign tables.

Any ideas?

2012/11/8 Atri Sharma <atri.jiit@gmail.com>:

On 08-Nov-2012, at 13:35, "Albe Laurenz" <laurenz.albe@wien.gv.at> wrote:

Alexander Korotkov wrote:

2) You wrote that FDW can support or don't support write depending on

having corresponding functions.

However it's likely some tables of same FDW could be writable while

another are not. I think we should

have some mechanism for FDW telling whether particular table is

writable.

I think that this would best be handled by a table option,
if necessary.
That allows maximum flexibility for the design of the FDW.
In many cases it might be enough if the foreign data source
raises an error on a write request.

Yours,
Laurenz Albe

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

+1

I agree, we should have a system where if the foreign data source raises an error on write, FDW can raise corresponding error on PostgreSQL side.exposing this as a table option is IMHO a bit risky, and the user may not know whether the foreign data source will accept writes or not.

Atri

--
KaiGai Kohei <kaigai@kaigai.gr.jp>

Attachments:

pgsql-v9.3-writable-fdw-poc.v4.patchapplication/octet-stream; name=pgsql-v9.3-writable-fdw-poc.v4.patchDownload
 contrib/file_fdw/file_fdw.c             | 177 +++++++++++++++++++++++++++++++-
 src/backend/commands/trigger.c          |  12 ++-
 src/backend/executor/execMain.c         |  34 +++++-
 src/backend/executor/nodeForeignscan.c  | 136 +++++++++++++++++++++++-
 src/backend/executor/nodeModifyTable.c  | 142 ++++++++++++++++++++-----
 src/backend/nodes/copyfuncs.c           |   1 +
 src/backend/nodes/outfuncs.c            |   1 +
 src/backend/optimizer/plan/createplan.c |  19 ++++
 src/backend/optimizer/plan/initsplan.c  |  10 +-
 src/backend/optimizer/plan/planmain.c   |   2 +-
 src/backend/optimizer/plan/planner.c    |   2 +-
 src/backend/optimizer/prep/prepunion.c  |   3 +-
 src/backend/optimizer/util/plancat.c    |  23 ++++-
 src/backend/optimizer/util/relnode.c    |   7 +-
 src/backend/rewrite/rewriteHandler.c    |  55 ++++++++--
 src/include/commands/trigger.h          |   8 +-
 src/include/foreign/fdwapi.h            |  25 +++++
 src/include/nodes/execnodes.h           |  13 ++-
 src/include/nodes/plannodes.h           |   1 +
 src/include/optimizer/pathnode.h        |   2 +-
 src/include/optimizer/plancat.h         |   2 +-
 src/include/optimizer/planmain.h        |   3 +-
 22 files changed, 609 insertions(+), 69 deletions(-)

diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
index 7ab3ed6..14991a3 100644
--- a/contrib/file_fdw/file_fdw.c
+++ b/contrib/file_fdw/file_fdw.c
@@ -34,6 +34,7 @@
 #include "optimizer/var.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
+#include "utils/lsyscache.h"
 
 PG_MODULE_MAGIC;
 
@@ -87,6 +88,7 @@ typedef struct FileFdwPlanState
 	List	   *options;		/* merged COPY options, excluding filename */
 	BlockNumber pages;			/* estimate of file's physical size */
 	double		ntuples;		/* estimate of number of rows in file */
+	DefElem	   *anum_rowid;		/* attribute number of rowid */
 } FileFdwPlanState;
 
 /*
@@ -97,6 +99,8 @@ typedef struct FileFdwExecutionState
 	char	   *filename;		/* file to read */
 	List	   *options;		/* merged COPY options, excluding filename */
 	CopyState	cstate;			/* state of reading file */
+	int			lineno;			/* pseudo ctid of the file */
+	AttrNumber	anum_rowid;		/* attribute number of rowid */
 } FileFdwExecutionState;
 
 /*
@@ -111,6 +115,10 @@ PG_FUNCTION_INFO_V1(file_fdw_validator);
 /*
  * FDW callback routines
  */
+static void fileGetForeignRelInfo(PlannerInfo *root,
+								  RelOptInfo *baserel,
+								  Oid foreigntableid,
+								  bool inhparent, List *targetList);
 static void fileGetForeignRelSize(PlannerInfo *root,
 					  RelOptInfo *baserel,
 					  Oid foreigntableid);
@@ -131,6 +139,19 @@ static void fileEndForeignScan(ForeignScanState *node);
 static bool fileAnalyzeForeignTable(Relation relation,
 						AcquireSampleRowsFunc *func,
 						BlockNumber *totalpages);
+static void fileBeginForeignModify(CmdType operation,
+								   ResultRelInfo *resultRelInfo,
+								   EState *estate,
+								   Plan *subplan,
+								   int eflags);
+static int	fileExecForeignInsert(ResultRelInfo *resultRelInfo,
+								  HeapTuple tuple);
+static int	fileExecForeignDelete(ResultRelInfo *resultRelInfo,
+								  Datum rowid);
+static int	fileExecForeignUpdate(ResultRelInfo *resultRelInfo,
+								  Datum rowid,
+								  HeapTuple tuple);
+static void fileEndForeignModify(ResultRelInfo *resultRelInfo);
 
 /*
  * Helper functions
@@ -169,7 +190,13 @@ file_fdw_handler(PG_FUNCTION_ARGS)
 	fdwroutine->IterateForeignScan = fileIterateForeignScan;
 	fdwroutine->ReScanForeignScan = fileReScanForeignScan;
 	fdwroutine->EndForeignScan = fileEndForeignScan;
+	fdwroutine->GetForeignRelInfo = fileGetForeignRelInfo;
 	fdwroutine->AnalyzeForeignTable = fileAnalyzeForeignTable;
+	fdwroutine->BeginForeignModify = fileBeginForeignModify;
+	fdwroutine->ExecForeignInsert = fileExecForeignInsert;
+	fdwroutine->ExecForeignDelete = fileExecForeignDelete;
+	fdwroutine->ExecForeignUpdate = fileExecForeignUpdate;
+	fdwroutine->EndForeignModify = fileEndForeignModify;
 
 	PG_RETURN_POINTER(fdwroutine);
 }
@@ -424,6 +451,47 @@ get_file_fdw_attribute_options(Oid relid)
 }
 
 /*
+ * fileGetForeignRelInfo
+ *		Adjust size of baserel->attr_needed according to var references
+ *		within targetList.
+ */
+static void
+fileGetForeignRelInfo(PlannerInfo *root,
+                      RelOptInfo *baserel,
+                      Oid foreigntableid,
+					  bool inhparent, List *targetList)
+{
+	FileFdwPlanState *fdw_private;
+	ListCell   *cell;
+
+	fdw_private = (FileFdwPlanState *) palloc0(sizeof(FileFdwPlanState));
+
+	foreach(cell, targetList)
+	{
+		TargetEntry	*tle = lfirst(cell);
+
+		if (tle->resjunk &&
+			tle->resname && strcmp(tle->resname, "rowid")==0)
+		{
+			Bitmapset  *temp = NULL;
+			AttrNumber	anum_rowid;
+			DefElem	   *defel;
+
+			pull_varattnos((Node *)tle, baserel->relid, &temp);
+			anum_rowid = bms_singleton_member(temp)
+				+ FirstLowInvalidHeapAttributeNumber;
+			/* adjust attr_needed of baserel */
+			if (anum_rowid > baserel->max_attr)
+				baserel->max_attr = anum_rowid;
+			defel = makeDefElem("anum_rowid",
+								(Node *)makeInteger(anum_rowid));
+			fdw_private->anum_rowid = defel;
+		}
+	}
+	baserel->fdw_private = fdw_private;
+}
+
+/*
  * fileGetForeignRelSize
  *		Obtain relation size estimates for a foreign table
  */
@@ -432,16 +500,14 @@ fileGetForeignRelSize(PlannerInfo *root,
 					  RelOptInfo *baserel,
 					  Oid foreigntableid)
 {
-	FileFdwPlanState *fdw_private;
+	FileFdwPlanState *fdw_private = (FileFdwPlanState *) baserel->fdw_private;
 
 	/*
 	 * Fetch options.  We only need filename at this point, but we might as
 	 * well get everything and not need to re-fetch it later in planning.
 	 */
-	fdw_private = (FileFdwPlanState *) palloc(sizeof(FileFdwPlanState));
 	fileGetOptions(foreigntableid,
 				   &fdw_private->filename, &fdw_private->options);
-	baserel->fdw_private = (void *) fdw_private;
 
 	/* Estimate relation size */
 	estimate_size(root, baserel, fdw_private);
@@ -473,6 +539,10 @@ fileGetForeignPaths(PlannerInfo *root,
 		coptions = list_make1(makeDefElem("convert_selectively",
 										  (Node *) columns));
 
+	/* save attribute number of rowid pseudo column */
+	if (fdw_private->anum_rowid)
+		coptions = lappend(coptions, fdw_private->anum_rowid);
+
 	/* Estimate costs */
 	estimate_costs(root, baserel, fdw_private,
 				   &startup_cost, &total_cost);
@@ -513,6 +583,35 @@ fileGetForeignPlan(PlannerInfo *root,
 	Index		scan_relid = baserel->relid;
 
 	/*
+	 * XXX - An evidence FDW module can know what kind of accesses are
+	 * requires on the target relation, if UPDATE, INSERT or DELETE.
+	 * It shall be also utilized to appropriate lock level on FDW
+	 * extensions that performs behalf on real RDBMS.
+	 */
+	if (root->parse->resultRelation == baserel->relid)
+	{
+		switch (root->parse->commandType)
+		{
+			case CMD_INSERT:
+				elog(INFO, "%s is the target relation of INSERT",
+					 get_rel_name(foreigntableid));
+				break;
+			case CMD_UPDATE:
+				elog(INFO, "%s is the target relation of UPDATE",
+					 get_rel_name(foreigntableid));
+				break;
+			case CMD_DELETE:
+				elog(INFO, "%s is the target relation of DELETE",
+                     get_rel_name(foreigntableid));
+				break;
+			default:
+				elog(INFO, "%s is the target relation of ??????",
+                     get_rel_name(foreigntableid));
+				break;
+		}
+	}
+
+	/*
 	 * We have no native ability to evaluate restriction clauses, so we just
 	 * put all the scan_clauses into the plan node's qual list for the
 	 * executor to check.  So all we have to do here is strip RestrictInfo
@@ -566,7 +665,10 @@ fileBeginForeignScan(ForeignScanState *node, int eflags)
 	ForeignScan *plan = (ForeignScan *) node->ss.ps.plan;
 	char	   *filename;
 	List	   *options;
+	ListCell   *cell;
+	ListCell   *prev;
 	CopyState	cstate;
+	AttrNumber	anum_rowid = InvalidAttrNumber;
 	FileFdwExecutionState *festate;
 
 	/*
@@ -582,6 +684,21 @@ fileBeginForeignScan(ForeignScanState *node, int eflags)
 	/* Add any options from the plan (currently only convert_selectively) */
 	options = list_concat(options, plan->fdw_private);
 
+	/* Fetch anum_rowid, if exist in options */
+	prev = NULL;
+	foreach (cell, options)
+	{
+		DefElem	   *defel = lfirst(cell);
+
+		if (strcmp(defel->defname, "anum_rowid") == 0)
+		{
+			anum_rowid = intVal(defel->arg);
+			options = list_delete_cell(options, cell, prev);
+			break;
+		}
+		prev = cell;
+	}
+
 	/*
 	 * Create CopyState from FDW options.  We always acquire all columns, so
 	 * as to match the expected ScanTupleSlot signature.
@@ -599,6 +716,8 @@ fileBeginForeignScan(ForeignScanState *node, int eflags)
 	festate->filename = filename;
 	festate->options = options;
 	festate->cstate = cstate;
+	festate->anum_rowid = anum_rowid;
+	festate->lineno = 1;
 
 	node->fdw_state = (void *) festate;
 }
@@ -639,7 +758,16 @@ fileIterateForeignScan(ForeignScanState *node)
 						 slot->tts_values, slot->tts_isnull,
 						 NULL);
 	if (found)
+	{
+		if (festate->anum_rowid != InvalidAttrNumber)
+		{
+			slot->tts_values[festate->anum_rowid - 1]
+				= Int32GetDatum(festate->lineno);
+			slot->tts_isnull[festate->anum_rowid - 1] = false;
+		}
 		ExecStoreVirtualTuple(slot);
+		festate->lineno++;
+	}
 
 	/* Remove error callback. */
 	error_context_stack = errcallback.previous;
@@ -662,6 +790,7 @@ fileReScanForeignScan(ForeignScanState *node)
 									festate->filename,
 									NIL,
 									festate->options);
+	festate->lineno = 1;
 }
 
 /*
@@ -717,6 +846,44 @@ fileAnalyzeForeignTable(Relation relation,
 	return true;
 }
 
+static void
+fileBeginForeignModify(CmdType operation,
+					   ResultRelInfo *resultRelInfo,
+					   EState *estate,
+					   Plan *subplan,
+					   int eflags)
+{
+	elog(INFO, "fdw_file: BeginForeignModify method");
+}
+
+static int
+fileExecForeignInsert(ResultRelInfo *resultRelInfo, HeapTuple tuple)
+{
+	elog(INFO, "fdw_file: INSERT");
+	return 1;
+}
+
+static int
+fileExecForeignDelete(ResultRelInfo *resultRelInfo, Datum rowid)
+{
+	elog(INFO, "fdw_file: DELETE (lineno = %u)", DatumGetInt32(rowid));
+	return 1;
+}
+
+static int
+fileExecForeignUpdate(ResultRelInfo *resultRelInfo,
+					  Datum rowid, HeapTuple tuple)
+{
+	elog(INFO, "fdw_file: UPDATE (lineno = %u)", DatumGetInt32(rowid));
+	return 1;
+}
+
+static void
+fileEndForeignModify(ResultRelInfo *resultRelInfo)
+{
+	elog(INFO, "fdw_file: EndForeignModify method");
+}
+
 /*
  * check_selective_binary_conversion
  *
@@ -789,8 +956,8 @@ check_selective_binary_conversion(RelOptInfo *baserel,
 			break;
 		}
 
-		/* Ignore system attributes. */
-		if (attnum < 0)
+		/* Ignore system or pseudo attributes. */
+		if (attnum < 0 || attnum > RelationGetNumberOfAttributes(rel))
 			continue;
 
 		/* Get user attributes. */
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 98b8207..c1d178c 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -2132,9 +2132,10 @@ ExecASDeleteTriggers(EState *estate, ResultRelInfo *relinfo)
 bool
 ExecBRDeleteTriggers(EState *estate, EPQState *epqstate,
 					 ResultRelInfo *relinfo,
-					 ItemPointer tupleid)
+					 Datum rowid)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
+	ItemPointer tupleid = (ItemPointer)DatumGetPointer(rowid);
 	bool		result = true;
 	TriggerData LocTriggerData;
 	HeapTuple	trigtuple;
@@ -2190,9 +2191,10 @@ ExecBRDeleteTriggers(EState *estate, EPQState *epqstate,
 
 void
 ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
-					 ItemPointer tupleid)
+					 Datum rowid)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
+	ItemPointer	tupleid = (ItemPointer)DatumGetPointer(rowid);
 
 	if (trigdesc && trigdesc->trig_delete_after_row)
 	{
@@ -2317,11 +2319,12 @@ ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
 TupleTableSlot *
 ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
 					 ResultRelInfo *relinfo,
-					 ItemPointer tupleid, TupleTableSlot *slot)
+					 Datum rowid, TupleTableSlot *slot)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 	HeapTuple	slottuple = ExecMaterializeSlot(slot);
 	HeapTuple	newtuple = slottuple;
+	ItemPointer	tupleid = (ItemPointer)DatumGetPointer(rowid);
 	TriggerData LocTriggerData;
 	HeapTuple	trigtuple;
 	HeapTuple	oldtuple;
@@ -2414,10 +2417,11 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
 
 void
 ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
-					 ItemPointer tupleid, HeapTuple newtuple,
+					 Datum rowid, HeapTuple newtuple,
 					 List *recheckIndexes)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
+	ItemPointer	tupleid = (ItemPointer)DatumGetPointer(rowid);
 
 	if (trigdesc && trigdesc->trig_update_after_row)
 	{
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index dbd3755..ccc1f3a 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"
@@ -934,6 +935,7 @@ void
 CheckValidResultRel(Relation resultRel, CmdType operation)
 {
 	TriggerDesc *trigDesc = resultRel->trigdesc;
+	FdwRoutine	*fdwroutine;
 
 	switch (resultRel->rd_rel->relkind)
 	{
@@ -985,10 +987,34 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
 			}
 			break;
 		case RELKIND_FOREIGN_TABLE:
-			ereport(ERROR,
-					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("cannot change foreign table \"%s\"",
-							RelationGetRelationName(resultRel))));
+			fdwroutine = GetFdwRoutineByRelId(RelationGetRelid(resultRel));
+			switch (operation)
+			{
+				case CMD_INSERT:
+					if (!fdwroutine || !fdwroutine->ExecForeignInsert)
+						ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("cannot insert into foreign table \"%s\"",
+									RelationGetRelationName(resultRel))));
+					break;
+				case CMD_UPDATE:
+					if (!fdwroutine || !fdwroutine->ExecForeignUpdate)
+						ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("cannot update foreign table \"%s\"",
+									RelationGetRelationName(resultRel))));
+					break;
+				case CMD_DELETE:
+					if (!fdwroutine || !fdwroutine->ExecForeignDelete)
+						ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("cannot delete from foreign table \"%s\"",
+									RelationGetRelationName(resultRel))));
+					break;
+				default:
+					elog(ERROR, "unrecognized CmdType: %d", (int) operation);
+					break;
+			}
 			break;
 		default:
 			ereport(ERROR,
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 9204859..1ee626f 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -25,6 +25,7 @@
 #include "executor/executor.h"
 #include "executor/nodeForeignscan.h"
 #include "foreign/fdwapi.h"
+#include "nodes/nodeFuncs.h"
 #include "utils/rel.h"
 
 static TupleTableSlot *ForeignNext(ForeignScanState *node);
@@ -93,6 +94,133 @@ ExecForeignScan(ForeignScanState *node)
 					(ExecScanRecheckMtd) ForeignRecheck);
 }
 
+/*
+ * pseudo_column_walker
+ *
+ * helper routine of GetPseudoTupleDesc. It pulls Var nodes that reference
+ * pseudo columns from targetlis of the relation
+ */
+typedef struct
+{
+	Relation	relation;
+	Index		varno;
+	List	   *pcolumns;
+	AttrNumber	max_attno;
+} pseudo_column_walker_context;
+
+static bool
+pseudo_column_walker(Node *node, pseudo_column_walker_context *context)
+{
+	if (node == NULL)
+		return false;
+	if (IsA(node, Var))
+	{
+		Var		   *var = (Var *) node;
+		ListCell   *cell;
+
+		if (var->varno == context->varno && var->varlevelsup == 0 &&
+			var->varattno > RelationGetNumberOfAttributes(context->relation))
+		{
+			foreach (cell, context->pcolumns)
+			{
+				Var	   *temp = lfirst(cell);
+
+				if (temp->varattno == var->varattno)
+				{
+					if (!equal(var, temp))
+						elog(ERROR, "asymmetric pseudo column appeared");
+					break;
+				}
+			}
+			if (!cell)
+			{
+				context->pcolumns = lappend(context->pcolumns, var);
+				if (var->varattno > context->max_attno)
+					context->max_attno = var->varattno;
+			}
+		}
+		return false;
+	}
+
+	/* Should not find an unplanned subquery */
+	Assert(!IsA(node, Query));
+
+	return expression_tree_walker(node, pseudo_column_walker,
+								  (void *)context);
+}
+
+/*
+ * GetPseudoTupleDesc
+ *
+ * It generates TupleDesc structure including pseudo-columns if required.
+ */
+static TupleDesc
+GetPseudoTupleDesc(ForeignScan *node, Relation relation)
+{
+	pseudo_column_walker_context context;
+	List	   *target_list = node->scan.plan.targetlist;
+	TupleDesc	tupdesc;
+	AttrNumber	attno;
+	ListCell   *cell;
+	ListCell   *prev;
+	bool		hasoid;
+
+	context.relation = relation;
+	context.varno = node->scan.scanrelid;
+	context.pcolumns = NIL;
+	context.max_attno = -1;
+
+	pseudo_column_walker((Node *)target_list, (void *)&context);
+	Assert(context.max_attno > RelationGetNumberOfAttributes(relation));
+
+	hasoid = RelationGetForm(relation)->relhasoids;
+	tupdesc = CreateTemplateTupleDesc(context.max_attno, hasoid);
+
+	for (attno = 1; attno <= context.max_attno; attno++)
+	{
+		/* case of regular columns */
+		if (attno <= RelationGetNumberOfAttributes(relation))
+		{
+			memcpy(tupdesc->attrs[attno - 1],
+				   RelationGetDescr(relation)->attrs[attno - 1],
+				   ATTRIBUTE_FIXED_PART_SIZE);
+			continue;
+		}
+
+		/* case of pseudo columns */
+		prev = NULL;
+		foreach (cell, context.pcolumns)
+		{
+			Var	   *var = lfirst(cell);
+
+			if (var->varattno == attno)
+			{
+				char		namebuf[NAMEDATALEN];
+
+				snprintf(namebuf, sizeof(namebuf),
+						 "pseudo_column_%d", attno);
+
+				TupleDescInitEntry(tupdesc,
+								   attno,
+								   namebuf,
+								   var->vartype,
+								   var->vartypmod,
+								   0);
+				TupleDescInitEntryCollation(tupdesc,
+											attno,
+											var->varcollid);
+				context.pcolumns
+					= list_delete_cell(context.pcolumns, cell, prev);
+				break;
+			}
+			prev = cell;
+		}
+		if (!cell)
+			elog(ERROR, "pseudo column %d of %s not in target list",
+				 attno, RelationGetRelationName(relation));
+	}
+	return tupdesc;
+}
 
 /* ----------------------------------------------------------------
  *		ExecInitForeignScan
@@ -103,6 +231,7 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
 {
 	ForeignScanState *scanstate;
 	Relation	currentRelation;
+	TupleDesc	tupdesc;
 	FdwRoutine *fdwroutine;
 
 	/* check for unsupported flags */
@@ -149,7 +278,12 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
 	/*
 	 * get the scan type from the relation descriptor.
 	 */
-	ExecAssignScanType(&scanstate->ss, RelationGetDescr(currentRelation));
+	if (node->fsPseudoCol)
+		tupdesc = GetPseudoTupleDesc(node, currentRelation);
+	else
+		tupdesc = RelationGetDescr(currentRelation);
+
+	ExecAssignScanType(&scanstate->ss, tupdesc);
 
 	/*
 	 * Initialize result tuple type and projection info.
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d31015c..bcb527c 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"
@@ -170,6 +171,7 @@ ExecInsert(TupleTableSlot *slot,
 	Relation	resultRelationDesc;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
+	int			num_rows = 1;
 
 	/*
 	 * get the heap tuple out of the tuple table slot, making sure we have a
@@ -225,6 +227,14 @@ ExecInsert(TupleTableSlot *slot,
 
 		newId = InvalidOid;
 	}
+	else if (resultRelInfo->ri_fdwroutine)
+	{
+		FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+		num_rows = fdwroutine->ExecForeignInsert(resultRelInfo, tuple);
+
+		newId = InvalidOid;
+	}
 	else
 	{
 		/*
@@ -252,7 +262,7 @@ ExecInsert(TupleTableSlot *slot,
 
 	if (canSetTag)
 	{
-		(estate->es_processed)++;
+		(estate->es_processed) += num_rows;
 		estate->es_lastoid = newId;
 		setLastTid(&(tuple->t_self));
 	}
@@ -285,7 +295,7 @@ ExecInsert(TupleTableSlot *slot,
  * ----------------------------------------------------------------
  */
 static TupleTableSlot *
-ExecDelete(ItemPointer tupleid,
+ExecDelete(Datum rowid,
 		   HeapTupleHeader oldtuple,
 		   TupleTableSlot *planSlot,
 		   EPQState *epqstate,
@@ -296,6 +306,7 @@ ExecDelete(ItemPointer tupleid,
 	Relation	resultRelationDesc;
 	HTSU_Result result;
 	HeapUpdateFailureData hufd;
+	int			num_rows = 1;
 
 	/*
 	 * get information on the (current) result relation
@@ -310,7 +321,7 @@ ExecDelete(ItemPointer tupleid,
 		bool		dodelete;
 
 		dodelete = ExecBRDeleteTriggers(estate, epqstate, resultRelInfo,
-										tupleid);
+										rowid);
 
 		if (!dodelete)			/* "do nothing" */
 			return NULL;
@@ -334,8 +345,16 @@ ExecDelete(ItemPointer tupleid,
 		if (!dodelete)			/* "do nothing" */
 			return NULL;
 	}
+	else if (resultRelInfo->ri_fdwroutine)
+	{
+		FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+		num_rows = fdwroutine->ExecForeignDelete(resultRelInfo, rowid);
+	}
 	else
 	{
+		ItemPointer	tupleid = (ItemPointer) DatumGetPointer(rowid);
+
 		/*
 		 * delete the tuple
 		 *
@@ -430,10 +449,10 @@ ldelete:;
 	}
 
 	if (canSetTag)
-		(estate->es_processed)++;
+		(estate->es_processed) += num_rows;
 
 	/* AFTER ROW DELETE Triggers */
-	ExecARDeleteTriggers(estate, resultRelInfo, tupleid);
+	ExecARDeleteTriggers(estate, resultRelInfo, rowid);
 
 	/* Process RETURNING if present */
 	if (resultRelInfo->ri_projectReturning)
@@ -457,7 +476,8 @@ ldelete:;
 		}
 		else
 		{
-			deltuple.t_self = *tupleid;
+			ItemPointerCopy((ItemPointer)DatumGetPointer(rowid),
+							&deltuple.t_self);
 			if (!heap_fetch(resultRelationDesc, SnapshotAny,
 							&deltuple, &delbuffer, false, NULL))
 				elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
@@ -499,7 +519,7 @@ ldelete:;
  * ----------------------------------------------------------------
  */
 static TupleTableSlot *
-ExecUpdate(ItemPointer tupleid,
+ExecUpdate(Datum rowid,
 		   HeapTupleHeader oldtuple,
 		   TupleTableSlot *slot,
 		   TupleTableSlot *planSlot,
@@ -513,6 +533,7 @@ ExecUpdate(ItemPointer tupleid,
 	HTSU_Result result;
 	HeapUpdateFailureData hufd;
 	List	   *recheckIndexes = NIL;
+	int			num_rows = 1;
 
 	/*
 	 * abort the operation if not running transactions
@@ -537,7 +558,7 @@ ExecUpdate(ItemPointer tupleid,
 		resultRelInfo->ri_TrigDesc->trig_update_before_row)
 	{
 		slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
-									tupleid, slot);
+									rowid, slot);
 
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
@@ -567,8 +588,16 @@ ExecUpdate(ItemPointer tupleid,
 		/* trigger might have changed tuple */
 		tuple = ExecMaterializeSlot(slot);
 	}
+	else if (resultRelInfo->ri_fdwroutine)
+	{
+		FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+		num_rows = fdwroutine->ExecForeignUpdate(resultRelInfo, rowid, tuple);
+	}
 	else
 	{
+		ItemPointer	tupleid = (ItemPointer) DatumGetPointer(rowid);
+
 		/*
 		 * Check the constraints of the tuple
 		 *
@@ -687,10 +716,10 @@ lreplace:;
 	}
 
 	if (canSetTag)
-		(estate->es_processed)++;
+		(estate->es_processed) += num_rows;
 
 	/* AFTER ROW UPDATE Triggers */
-	ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple,
+	ExecARUpdateTriggers(estate, resultRelInfo, rowid, tuple,
 						 recheckIndexes);
 
 	list_free(recheckIndexes);
@@ -771,6 +800,7 @@ ExecModifyTable(ModifyTableState *node)
 	TupleTableSlot *planSlot;
 	ItemPointer tupleid = NULL;
 	ItemPointerData tuple_ctid;
+	Datum		rowid = 0;
 	HeapTupleHeader oldtuple = NULL;
 
 	/*
@@ -859,17 +889,19 @@ ExecModifyTable(ModifyTableState *node)
 		if (junkfilter != NULL)
 		{
 			/*
-			 * extract the 'ctid' or 'wholerow' junk attribute.
+			 * extract the 'ctid', 'rowid' or 'wholerow' junk attribute.
 			 */
 			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,
+												 junkfilter->jf_junkRowidNo,
 												 &isNull);
 					/* shouldn't ever get a null result... */
 					if (isNull)
@@ -877,13 +909,33 @@ ExecModifyTable(ModifyTableState *node)
 
 					tupleid = (ItemPointer) DatumGetPointer(datum);
 					tuple_ctid = *tupleid;		/* be sure we don't free
-												 * ctid!! */
-					tupleid = &tuple_ctid;
+												 * ctid ! */
+					rowid = PointerGetDatum(&tuple_ctid);
+				}
+				else if (relkind == RELKIND_FOREIGN_TABLE)
+				{
+					datum = ExecGetJunkAttribute(slot,
+												 junkfilter->jf_junkRowidNo,
+												 &isNull);
+					/* shouldn't ever get a null result... */
+					if (isNull)
+						elog(ERROR, "rowid is NULL");
+
+					rowid = datum;
+
+					datum = ExecGetJunkAttribute(slot,
+												 junkfilter->jf_junkRecordNo,
+												 &isNull);
+					/* shouldn't ever get a null result... */
+					if (isNull)
+						elog(ERROR, "wholerow is NULL");
+
+					oldtuple = DatumGetHeapTupleHeader(datum);
 				}
 				else
 				{
 					datum = ExecGetJunkAttribute(slot,
-												 junkfilter->jf_junkAttNo,
+												 junkfilter->jf_junkRecordNo,
 												 &isNull);
 					/* shouldn't ever get a null result... */
 					if (isNull)
@@ -906,11 +958,11 @@ ExecModifyTable(ModifyTableState *node)
 				slot = ExecInsert(slot, planSlot, estate, node->canSetTag);
 				break;
 			case CMD_UPDATE:
-				slot = ExecUpdate(tupleid, oldtuple, slot, planSlot,
+				slot = ExecUpdate(rowid, oldtuple, slot, planSlot,
 								&node->mt_epqstate, estate, node->canSetTag);
 				break;
 			case CMD_DELETE:
-				slot = ExecDelete(tupleid, oldtuple, planSlot,
+				slot = ExecDelete(rowid, oldtuple, planSlot,
 								&node->mt_epqstate, estate, node->canSetTag);
 				break;
 			default:
@@ -1022,6 +1074,22 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		estate->es_result_relation_info = resultRelInfo;
 		mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
 
+		/*
+		 * Also tells FDW extensions to init the plan for this result rel
+		 */
+		if (RelationGetForm(resultRelInfo->ri_RelationDesc)->relkind == RELKIND_FOREIGN_TABLE)
+		{
+			Oid		relid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+			FdwRoutine *fdwroutine = GetFdwRoutineByRelId(relid);
+
+			Assert(fdwroutine != NULL);
+			resultRelInfo->ri_fdwroutine = fdwroutine;
+			resultRelInfo->ri_fdw_state = NULL;
+
+			if (fdwroutine->BeginForeignModify)
+				fdwroutine->BeginForeignModify(operation, resultRelInfo,
+											   estate, subplan, eflags);
+		}
 		resultRelInfo++;
 		i++;
 	}
@@ -1163,6 +1231,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			for (i = 0; i < nplans; i++)
 			{
 				JunkFilter *j;
+				char		relkind =
+				    RelationGetForm(resultRelInfo->ri_RelationDesc)->relkind;
 
 				subplan = mtstate->mt_plans[i]->plan;
 				if (operation == CMD_INSERT || operation == CMD_UPDATE)
@@ -1176,16 +1246,27 @@ 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)
+					if (relkind == RELKIND_RELATION)
 					{
-						j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
-						if (!AttributeNumberIsValid(j->jf_junkAttNo))
+						j->jf_junkRowidNo = ExecFindJunkAttribute(j, "ctid");
+						if (!AttributeNumberIsValid(j->jf_junkRowidNo))
 							elog(ERROR, "could not find junk ctid column");
 					}
+					else if (relkind == RELKIND_FOREIGN_TABLE)
+					{
+						j->jf_junkRowidNo = ExecFindJunkAttribute(j, "rowid");
+						if (!AttributeNumberIsValid(j->jf_junkRowidNo))
+							elog(ERROR, "could not find junk rowid column");
+						j->jf_junkRecordNo
+							= ExecFindJunkAttribute(j, "record");
+						if (!AttributeNumberIsValid(j->jf_junkRecordNo))
+							elog(ERROR, "could not find junk wholerow column");
+					}
 					else
 					{
-						j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
-						if (!AttributeNumberIsValid(j->jf_junkAttNo))
+						j->jf_junkRecordNo
+							= ExecFindJunkAttribute(j, "wholerow");
+						if (!AttributeNumberIsValid(j->jf_junkRecordNo))
 							elog(ERROR, "could not find junk wholerow column");
 					}
 				}
@@ -1239,6 +1320,21 @@ ExecEndModifyTable(ModifyTableState *node)
 {
 	int			i;
 
+	/* Let the FDW shut dowm */
+	for (i=0; i < node->ps.state->es_num_result_relations; i++)
+	{
+		ResultRelInfo  *resultRelInfo
+			= &node->ps.state->es_result_relations[i];
+
+		if (resultRelInfo->ri_fdwroutine)
+		{
+			FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+			if (fdwroutine->EndForeignModify)
+				fdwroutine->EndForeignModify(resultRelInfo);
+		}
+	}
+
 	/*
 	 * Free the exprcontext
 	 */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 9387ee9..2dd6261 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -594,6 +594,7 @@ _copyForeignScan(const ForeignScan *from)
 	COPY_NODE_FIELD(fdw_exprs);
 	COPY_NODE_FIELD(fdw_private);
 	COPY_SCALAR_FIELD(fsSystemCol);
+	COPY_SCALAR_FIELD(fsPseudoCol);
 
 	return newnode;
 }
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 35c6287..39130ec 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -562,6 +562,7 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
 	WRITE_NODE_FIELD(fdw_exprs);
 	WRITE_NODE_FIELD(fdw_private);
 	WRITE_BOOL_FIELD(fsSystemCol);
+	WRITE_BOOL_FIELD(fsPseudoCol);
 }
 
 static void
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 030f420..ff3b9ff 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -39,6 +39,7 @@
 #include "parser/parse_clause.h"
 #include "parser/parsetree.h"
 #include "utils/lsyscache.h"
+#include "utils/rel.h"
 
 
 static Plan *create_plan_recurse(PlannerInfo *root, Path *best_path);
@@ -1943,6 +1944,8 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 	RelOptInfo *rel = best_path->path.parent;
 	Index		scan_relid = rel->relid;
 	RangeTblEntry *rte;
+	Relation	relation;
+	AttrNumber	num_attrs;
 	int			i;
 
 	/* it should be a base rel... */
@@ -2001,6 +2004,22 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 		}
 	}
 
+	/*
+	 * Also, detect whether any pseudo columns are requested from rel.
+	 */
+	relation = heap_open(rte->relid, NoLock);
+	scan_plan->fsPseudoCol = false;
+	num_attrs = RelationGetNumberOfAttributes(relation);
+	for (i = num_attrs + 1; i <= rel->max_attr; i++)
+	{
+		if (!bms_is_empty(rel->attr_needed[i - rel->min_attr]))
+		{
+			scan_plan->fsPseudoCol = true;
+			break;
+		}
+	}
+	heap_close(relation, NoLock);
+
 	return scan_plan;
 }
 
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index bd719b5..57ba192 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -85,7 +85,7 @@ static void check_hashjoinable(RestrictInfo *restrictinfo);
  * "other rel" RelOptInfos for the members of any appendrels we find here.)
  */
 void
-add_base_rels_to_query(PlannerInfo *root, Node *jtnode)
+add_base_rels_to_query(PlannerInfo *root, List *tlist, Node *jtnode)
 {
 	if (jtnode == NULL)
 		return;
@@ -93,7 +93,7 @@ add_base_rels_to_query(PlannerInfo *root, Node *jtnode)
 	{
 		int			varno = ((RangeTblRef *) jtnode)->rtindex;
 
-		(void) build_simple_rel(root, varno, RELOPT_BASEREL);
+		(void) build_simple_rel(root, varno, tlist, RELOPT_BASEREL);
 	}
 	else if (IsA(jtnode, FromExpr))
 	{
@@ -101,14 +101,14 @@ add_base_rels_to_query(PlannerInfo *root, Node *jtnode)
 		ListCell   *l;
 
 		foreach(l, f->fromlist)
-			add_base_rels_to_query(root, lfirst(l));
+			add_base_rels_to_query(root, tlist, lfirst(l));
 	}
 	else if (IsA(jtnode, JoinExpr))
 	{
 		JoinExpr   *j = (JoinExpr *) jtnode;
 
-		add_base_rels_to_query(root, j->larg);
-		add_base_rels_to_query(root, j->rarg);
+		add_base_rels_to_query(root, tlist, j->larg);
+		add_base_rels_to_query(root, tlist, j->rarg);
 	}
 	else
 		elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
index c2488a4..2d292dd 100644
--- a/src/backend/optimizer/plan/planmain.c
+++ b/src/backend/optimizer/plan/planmain.c
@@ -163,7 +163,7 @@ query_planner(PlannerInfo *root, List *tlist,
 	 * rangetable may contain RTEs for rels not actively part of the query,
 	 * for example views.  We don't want to make RelOptInfos for them.
 	 */
-	add_base_rels_to_query(root, (Node *) parse->jointree);
+	add_base_rels_to_query(root, tlist, (Node *) parse->jointree);
 
 	/*
 	 * Examine the targetlist and join tree, adding entries to baserel
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b61005f..19e5503 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -3385,7 +3385,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 	setup_simple_rel_arrays(root);
 
 	/* Build RelOptInfo */
-	rel = build_simple_rel(root, 1, RELOPT_BASEREL);
+	rel = build_simple_rel(root, 1, NIL, RELOPT_BASEREL);
 
 	/* Locate IndexOptInfo for the target index */
 	indexInfo = NULL;
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index b91e9f4..61bc447 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -236,7 +236,8 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 		 * used for anything here, but it carries the subroot data structures
 		 * forward to setrefs.c processing.
 		 */
-		rel = build_simple_rel(root, rtr->rtindex, RELOPT_BASEREL);
+		rel = build_simple_rel(root, rtr->rtindex, refnames_tlist,
+							   RELOPT_BASEREL);
 
 		/* plan_params should not be in use in current query level */
 		Assert(root->plan_params == NIL);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index abcd0ee..a37ab35 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -25,6 +25,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/heap.h"
+#include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/clauses.h"
@@ -80,7 +81,7 @@ static List *build_index_tlist(PlannerInfo *root, IndexOptInfo *index,
  */
 void
 get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
-				  RelOptInfo *rel)
+				  List *tlist, RelOptInfo *rel)
 {
 	Index		varno = rel->relid;
 	Relation	relation;
@@ -104,6 +105,26 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 	rel->max_attr = RelationGetNumberOfAttributes(relation);
 	rel->reltablespace = RelationGetForm(relation)->reltablespace;
 
+	/*
+	 * Adjust width of attr_needed slot in case when FDW extension wants
+	 * or needs to return pseudo-columns also, not only columns in its
+	 * table definition.
+	 * GetForeignRelInfo, an optional FDW handler, enables FDW extension
+	 * to save properties of pseudo-column on its private field.
+	 * When foreign-table is the target of UPDATE/DELETE, query-rewritter
+	 * injects "rowid" pseudo-column to track remote row to be modified,
+	 * so FDW has to track which varattno shall perform as "rowid".
+	 */
+	if (RelationGetForm(relation)->relkind == RELKIND_FOREIGN_TABLE)
+	{
+		FdwRoutine *fdwroutine = GetFdwRoutineByRelId(relationObjectId);
+
+		if (fdwroutine->GetForeignRelInfo)
+			fdwroutine->GetForeignRelInfo(root, rel,
+										  relationObjectId,
+										  inhparent, tlist);
+	}
+
 	Assert(rel->max_attr >= rel->min_attr);
 	rel->attr_needed = (Relids *)
 		palloc0((rel->max_attr - rel->min_attr + 1) * sizeof(Relids));
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index f724714..84b674c 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -80,7 +80,8 @@ setup_simple_rel_arrays(PlannerInfo *root)
  *	  Construct a new RelOptInfo for a base relation or 'other' relation.
  */
 RelOptInfo *
-build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
+build_simple_rel(PlannerInfo *root, int relid, List *tlist,
+				 RelOptKind reloptkind)
 {
 	RelOptInfo *rel;
 	RangeTblEntry *rte;
@@ -133,7 +134,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
 	{
 		case RTE_RELATION:
 			/* Table --- retrieve statistics from the system catalogs */
-			get_relation_info(root, rte->relid, rte->inh, rel);
+			get_relation_info(root, rte->relid, rte->inh, tlist, rel);
 			break;
 		case RTE_SUBQUERY:
 		case RTE_FUNCTION:
@@ -180,7 +181,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
 			if (appinfo->parent_relid != relid)
 				continue;
 
-			(void) build_simple_rel(root, appinfo->child_relid,
+			(void) build_simple_rel(root, appinfo->child_relid, tlist,
 									RELOPT_OTHER_MEMBER_REL);
 		}
 	}
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index b785c26..5c5a418 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1165,7 +1165,10 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 					Relation target_relation)
 {
 	Var		   *var;
-	const char *attrname;
+	List	   *varList;
+	List	   *attNameList;
+	ListCell   *cell1;
+	ListCell   *cell2;
 	TargetEntry *tle;
 
 	if (target_relation->rd_rel->relkind == RELKIND_RELATION)
@@ -1179,8 +1182,35 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 					  -1,
 					  InvalidOid,
 					  0);
+		varList = list_make1(var);
+		attNameList = list_make1("ctid");
+	}
+	else if (target_relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+	{
+		/*
+		 * Emit Rowid so that executor can find the row to update or delete.
+		 */
+		var = makeVar(parsetree->resultRelation,
+					  RelationGetNumberOfAttributes(target_relation) + 1,
+					  INTERNALOID,
+					  -1,
+					  InvalidOid,
+					  0);
+		varList = list_make1(var);
 
-		attrname = "ctid";
+		/*
+		 * Emit generic record Var so that executor will have the "old" view
+		 * row to pass the RETURNING clause (or upcoming triggers).
+		 */
+		var = makeVar(parsetree->resultRelation,
+					  InvalidAttrNumber,
+					  RECORDOID,
+					  -1,
+					  InvalidOid,
+					  0);
+		varList = lappend(varList, var);
+
+		attNameList = list_make2("rowid", "record");
 	}
 	else
 	{
@@ -1192,16 +1222,21 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 							  parsetree->resultRelation,
 							  0,
 							  false);
-
-		attrname = "wholerow";
+		varList = list_make1(var);
+		attNameList = list_make1("wholerow");
 	}
 
-	tle = makeTargetEntry((Expr *) var,
-						  list_length(parsetree->targetList) + 1,
-						  pstrdup(attrname),
-						  true);
-
-	parsetree->targetList = lappend(parsetree->targetList, tle);
+	/*
+	 * Append them to targetList
+	 */
+	forboth (cell1, varList, cell2, attNameList)
+	{
+		tle = makeTargetEntry((Expr *)lfirst(cell1),
+							  list_length(parsetree->targetList) + 1,
+							  pstrdup((const char *)lfirst(cell2)),
+							  true);
+		parsetree->targetList = lappend(parsetree->targetList, tle);
+	}
 }
 
 
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index e790b9d..8370e05 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -147,10 +147,10 @@ extern void ExecASDeleteTriggers(EState *estate,
 extern bool ExecBRDeleteTriggers(EState *estate,
 					 EPQState *epqstate,
 					 ResultRelInfo *relinfo,
-					 ItemPointer tupleid);
+					 Datum rowid);
 extern void ExecARDeleteTriggers(EState *estate,
 					 ResultRelInfo *relinfo,
-					 ItemPointer tupleid);
+					 Datum rowid);
 extern bool ExecIRDeleteTriggers(EState *estate,
 					 ResultRelInfo *relinfo,
 					 HeapTuple trigtuple);
@@ -161,11 +161,11 @@ extern void ExecASUpdateTriggers(EState *estate,
 extern TupleTableSlot *ExecBRUpdateTriggers(EState *estate,
 					 EPQState *epqstate,
 					 ResultRelInfo *relinfo,
-					 ItemPointer tupleid,
+					 Datum rowid,
 					 TupleTableSlot *slot);
 extern void ExecARUpdateTriggers(EState *estate,
 					 ResultRelInfo *relinfo,
-					 ItemPointer tupleid,
+					 Datum rowid,
 					 HeapTuple newtuple,
 					 List *recheckIndexes);
 extern TupleTableSlot *ExecIRUpdateTriggers(EState *estate,
diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
index 721cd25..8b29532 100644
--- a/src/include/foreign/fdwapi.h
+++ b/src/include/foreign/fdwapi.h
@@ -22,6 +22,11 @@ struct ExplainState;
 /*
  * Callback function signatures --- see fdwhandler.sgml for more info.
  */
+typedef void (*GetForeignRelInfo_function) (PlannerInfo *root,
+											RelOptInfo *baserel,
+											Oid foreigntableid,
+											bool inhparent,
+											List *targetList);
 
 typedef void (*GetForeignRelSize_function) (PlannerInfo *root,
 														RelOptInfo *baserel,
@@ -59,6 +64,20 @@ typedef bool (*AnalyzeForeignTable_function) (Relation relation,
 												 AcquireSampleRowsFunc *func,
 													BlockNumber *totalpages);
 
+typedef void (*BeginForeignModify_function) (CmdType operation,
+											 ResultRelInfo *resultRelInfo,
+											 EState *estate,
+											 Plan *subplan,
+											 int eflags);
+typedef int	(*ExecForeignInsert_function) (ResultRelInfo *resultRelInfo,
+										   HeapTuple tuple);
+typedef int	(*ExecForeignDelete_function) (ResultRelInfo *resultRelInfo,
+										   Datum rowid);
+typedef int	(*ExecForeignUpdate_function) (ResultRelInfo *resultRelInfo,
+										   Datum rowid,
+										   HeapTuple tuple);
+typedef void (*EndForeignModify_function) (ResultRelInfo *resultRelInfo);
+
 /*
  * FdwRoutine is the struct returned by a foreign-data wrapper's handler
  * function.  It provides pointers to the callback functions needed by the
@@ -90,6 +109,12 @@ typedef struct FdwRoutine
 	 * not provided.
 	 */
 	AnalyzeForeignTable_function AnalyzeForeignTable;
+	GetForeignRelInfo_function GetForeignRelInfo;
+	BeginForeignModify_function	BeginForeignModify;
+	ExecForeignInsert_function ExecForeignInsert;
+	ExecForeignDelete_function ExecForeignDelete;
+	ExecForeignUpdate_function ExecForeignUpdate;
+	EndForeignModify_function EndForeignModify;
 } FdwRoutine;
 
 
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index fec07b8..d8fa299 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -268,9 +268,11 @@ typedef struct ProjectionInfo
  *						attribute numbers of the "original" tuple and the
  *						attribute numbers of the "clean" tuple.
  *	  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
+ *	  jf_junkRowidNo:	not used by junkfilter code.  Can be used by caller
+ *						to remember the attno of to track a particular tuple
+ *						being updated or deleted.
  *						(execMain.c stores the "ctid" attno here).
+ *	  jf_junkRecordNo:	Also, the attno of whole-row reference.
  * ----------------
  */
 typedef struct JunkFilter
@@ -280,7 +282,8 @@ typedef struct JunkFilter
 	TupleDesc	jf_cleanTupType;
 	AttrNumber *jf_cleanMap;
 	TupleTableSlot *jf_resultSlot;
-	AttrNumber	jf_junkAttNo;
+	AttrNumber	jf_junkRowidNo;
+	AttrNumber	jf_junkRecordNo;
 } JunkFilter;
 
 /* ----------------
@@ -303,6 +306,8 @@ typedef struct JunkFilter
  *		ConstraintExprs			array of constraint-checking expr states
  *		junkFilter				for removing junk attributes from tuples
  *		projectReturning		for computing a RETURNING list
+ *		fdwroutine				FDW callbacks if foreign table
+ *		fdw_state				opaque state of FDW module, or NULL
  * ----------------
  */
 typedef struct ResultRelInfo
@@ -320,6 +325,8 @@ typedef struct ResultRelInfo
 	List	  **ri_ConstraintExprs;
 	JunkFilter *ri_junkFilter;
 	ProjectionInfo *ri_projectReturning;
+	struct FdwRoutine  *ri_fdwroutine;
+	void	   *ri_fdw_state;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index fb9a863..a724863 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -478,6 +478,7 @@ typedef struct ForeignScan
 	List	   *fdw_exprs;		/* expressions that FDW may evaluate */
 	List	   *fdw_private;	/* private data for FDW */
 	bool		fsSystemCol;	/* true if any "system column" is needed */
+	bool		fsPseudoCol;	/* true if any "pseudo column" is needed */
 } ForeignScan;
 
 
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index de889fb..adfc93d 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -133,7 +133,7 @@ extern Path *reparameterize_path(PlannerInfo *root, Path *path,
  */
 extern void setup_simple_rel_arrays(PlannerInfo *root);
 extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid,
-				 RelOptKind reloptkind);
+			   List *tlist, RelOptKind reloptkind);
 extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid);
 extern RelOptInfo *find_join_rel(PlannerInfo *root, Relids relids);
 extern RelOptInfo *build_join_rel(PlannerInfo *root,
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index e0d04db..c5fce41 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -26,7 +26,7 @@ extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook;
 
 
 extern void get_relation_info(PlannerInfo *root, Oid relationObjectId,
-				  bool inhparent, RelOptInfo *rel);
+				  bool inhparent, List *tlist, RelOptInfo *rel);
 
 extern void estimate_rel_size(Relation rel, int32 *attr_widths,
 				  BlockNumber *pages, double *tuples, double *allvisfrac);
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 0fe696c..ad7bbd6 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -90,7 +90,8 @@ extern bool is_projection_capable_plan(Plan *plan);
 extern int	from_collapse_limit;
 extern int	join_collapse_limit;
 
-extern void add_base_rels_to_query(PlannerInfo *root, Node *jtnode);
+extern void add_base_rels_to_query(PlannerInfo *root, List *tlist,
+								   Node *jtnode);
 extern void build_base_rel_tlists(PlannerInfo *root, List *final_tlist);
 extern void add_vars_to_targetlist(PlannerInfo *root, List *vars,
 					   Relids where_needed, bool create_new_ph);
#30Albe Laurenz
laurenz.albe@wien.gv.at
In reply to: Kohei KaiGai (#29)
Re: [v9.3] writable foreign tables

Kohei KaiGai wrote:

The attached patch is just a refreshed version for clean applying to
the latest tree.

As previous version doing, it makes pseudo enhancement on file_fdw
to print something about the supplied tuple on INSERT, UPDATE and
DELETE statement.

Basics:
-------

The patch applies cleanly, compiles without warnings and passes
regression tests.

I think that the functionality is highly desirable; judging from
the number of talks at pgConf EU about SQL/MED this is a hot
topic, and further development is welcome.

Testing the functionality:
--------------------------

I ran a few queries with the file_fdw and found this:

$ cat flatfile
1,Laurenz,1968-10-20
2,Renée,1975-09-03
3,Caroline,2009-01-26
4,Ray,2011-03-09
5,Stephan,2011-03-09

CREATE SERVER file FOREIGN DATA WRAPPER file_fdw;

CREATE FOREIGN TABLE flat(
id integer not null,
name varchar(20) not null,
birthday date not null
) SERVER file
OPTIONS (filename 'flatfile', format 'csv');

UPDATE flat SET name='' FROM flat f WHERE f.id = flat.id and f.name like '%e';
ERROR: bitmapset is empty

About the code:
---------------

I am not so happy with GetForeignRelInfo:
- The name seems ill-chosen from the FDW API side.
I guess that you chose the name because the function
is called from get_relation_info, but I think the name
should be more descriptive for the FDW API.
Something like PlanForeignRelRowid.

- I guess that every FDW that only needs "rowid" will
do exactly the same as your fileGetForeignRelInfo.
Why can't that be done in core?
The function could pass an AttrNumber for the rowid
to the FDW, and will receive a boolean return code
depending on whether the FDW plans to use rowid or not.
That would be more convenient for FDW authors.

- I guess the order is dictated by planner steps, but
it would be "nice to have" if GetForeignRelInfo were
not the first function to be called during planning.
That would make it easier for a FDW to support both
9.2 and 9.3 (fewer #ifdefs), because the FDW plan state
will probably have to be created in the first API
function.

I also wonder why you changed the signature of functions in
trigger.c. It changes an exposed API unnecessarily, unless
you plan to have trigger support for foreign tables.
That is currently not supported, and I think that such
changes should be part of the present patch.

Other than that, I like the patch.
I cannot give an educated opinion if the changes to planner
and executor are well done or not.

Other comments:
---------------

I hope I find time to implement the API in oracle_fdw to
be able to test it more thoroughly.

There is an interdependence with the "FDW for PostgreSQL" patch
in the commitfest. If both these patches get committed, the
FDW should be extended to support the new API. If nothing else,
this would greatly improve the ability to test the new API
and find out if it is well designed.

Here is one other idea. My GPU acceleration module (PG-Strom)
implements column-oriented data store underlying foreign table.
It might make sense to cut out this portion for proof-of-concept of
writable foreign tables.

Any ideas?

The best would be to have a patch on top of the PostgreSQL FDW
to be able to test. It need not be perfect.

Yours,
Laurenz Albe

#31Atri Sharma
atri.jiit@gmail.com
In reply to: Albe Laurenz (#30)
Re: [v9.3] writable foreign tables

Awesome.

I would love to implement this API in JDBC_FDW.

Atri

Sent from my iPad

On 16-Nov-2012, at 20:20, "Albe Laurenz" <laurenz.albe@wien.gv.at> wrote:

Show quoted text

Kohei KaiGai wrote:

The attached patch is just a refreshed version for clean applying to
the latest tree.

As previous version doing, it makes pseudo enhancement on file_fdw
to print something about the supplied tuple on INSERT, UPDATE and
DELETE statement.

Basics:
-------

The patch applies cleanly, compiles without warnings and passes
regression tests.

I think that the functionality is highly desirable; judging from
the number of talks at pgConf EU about SQL/MED this is a hot
topic, and further development is welcome.

Testing the functionality:
--------------------------

I ran a few queries with the file_fdw and found this:

$ cat flatfile
1,Laurenz,1968-10-20
2,Renée,1975-09-03
3,Caroline,2009-01-26
4,Ray,2011-03-09
5,Stephan,2011-03-09

CREATE SERVER file FOREIGN DATA WRAPPER file_fdw;

CREATE FOREIGN TABLE flat(
id integer not null,
name varchar(20) not null,
birthday date not null
) SERVER file
OPTIONS (filename 'flatfile', format 'csv');

UPDATE flat SET name='' FROM flat f WHERE f.id = flat.id and f.name like '%e';
ERROR: bitmapset is empty

About the code:
---------------

I am not so happy with GetForeignRelInfo:
- The name seems ill-chosen from the FDW API side.
I guess that you chose the name because the function
is called from get_relation_info, but I think the name
should be more descriptive for the FDW API.
Something like PlanForeignRelRowid.

- I guess that every FDW that only needs "rowid" will
do exactly the same as your fileGetForeignRelInfo.
Why can't that be done in core?
The function could pass an AttrNumber for the rowid
to the FDW, and will receive a boolean return code
depending on whether the FDW plans to use rowid or not.
That would be more convenient for FDW authors.

- I guess the order is dictated by planner steps, but
it would be "nice to have" if GetForeignRelInfo were
not the first function to be called during planning.
That would make it easier for a FDW to support both
9.2 and 9.3 (fewer #ifdefs), because the FDW plan state
will probably have to be created in the first API
function.

I also wonder why you changed the signature of functions in
trigger.c. It changes an exposed API unnecessarily, unless
you plan to have trigger support for foreign tables.
That is currently not supported, and I think that such
changes should be part of the present patch.

Other than that, I like the patch.
I cannot give an educated opinion if the changes to planner
and executor are well done or not.

Other comments:
---------------

I hope I find time to implement the API in oracle_fdw to
be able to test it more thoroughly.

There is an interdependence with the "FDW for PostgreSQL" patch
in the commitfest. If both these patches get committed, the
FDW should be extended to support the new API. If nothing else,
this would greatly improve the ability to test the new API
and find out if it is well designed.

Here is one other idea. My GPU acceleration module (PG-Strom)
implements column-oriented data store underlying foreign table.
It might make sense to cut out this portion for proof-of-concept of
writable foreign tables.

Any ideas?

The best would be to have a patch on top of the PostgreSQL FDW
to be able to test. It need not be perfect.

Yours,
Laurenz Albe

#32Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Albe Laurenz (#30)
Re: [v9.3] writable foreign tables

2012/11/16 Albe Laurenz <laurenz.albe@wien.gv.at>:

Kohei KaiGai wrote:

The attached patch is just a refreshed version for clean applying to
the latest tree.

As previous version doing, it makes pseudo enhancement on file_fdw
to print something about the supplied tuple on INSERT, UPDATE and
DELETE statement.

Basics:
-------

The patch applies cleanly, compiles without warnings and passes
regression tests.

I think that the functionality is highly desirable; judging from
the number of talks at pgConf EU about SQL/MED this is a hot
topic, and further development is welcome.

Testing the functionality:
--------------------------

I ran a few queries with the file_fdw and found this:

$ cat flatfile
1,Laurenz,1968-10-20
2,Renée,1975-09-03
3,Caroline,2009-01-26
4,Ray,2011-03-09
5,Stephan,2011-03-09

CREATE SERVER file FOREIGN DATA WRAPPER file_fdw;

CREATE FOREIGN TABLE flat(
id integer not null,
name varchar(20) not null,
birthday date not null
) SERVER file
OPTIONS (filename 'flatfile', format 'csv');

UPDATE flat SET name='' FROM flat f WHERE f.id = flat.id and f.name like '%e';
ERROR: bitmapset is empty

Hmm... I'll try to investigate the behavior.

About the code:
---------------

I am not so happy with GetForeignRelInfo:
- The name seems ill-chosen from the FDW API side.
I guess that you chose the name because the function
is called from get_relation_info, but I think the name
should be more descriptive for the FDW API.
Something like PlanForeignRelRowid.

Indeed, GetForeignRelInfo might give misleading impression
as if this routine collects widespread information about
target relation. So, how about GetForeignRelWidth() instead?

- I guess that every FDW that only needs "rowid" will
do exactly the same as your fileGetForeignRelInfo.
Why can't that be done in core?
The function could pass an AttrNumber for the rowid
to the FDW, and will receive a boolean return code
depending on whether the FDW plans to use rowid or not.
That would be more convenient for FDW authors.

This design tries to kill two-birds with one-stone.
It enables to add multiple number of pseudo-columns,
not only "rowid", and makes possible to push-down
complex calculation of target list into external computing
resource.

For example, when user gives the following query:

SELECT ((c1 - c2) * (c2 - c3))^2 FROM ftable

it contains a complex calculation in the target-list,
thus, it also takes CPU cycles of local process.

If we can replace the "((c1 - c2) * (c2 - c3))^2" by
a reference to a pseudo-column that also references
the calculation result on external node, it effectively
off-load CPU cycles.

In this case, all we need to do is (1) acquire a slot
for pseudo-column at GetForeignRelInfo (2) replace
TargetEntry::expr by Var node that reference this
pseudo-column.

It makes sense for performance optimization, so I don't
want to restrict this handler for "rowid" only.

- I guess the order is dictated by planner steps, but
it would be "nice to have" if GetForeignRelInfo were
not the first function to be called during planning.
That would make it easier for a FDW to support both
9.2 and 9.3 (fewer #ifdefs), because the FDW plan state
will probably have to be created in the first API
function.

The baserel->fdw_private should be initialized to NULL,
so it can perform as a mark whether private data is already
constructed, or not.

I also wonder why you changed the signature of functions in
trigger.c. It changes an exposed API unnecessarily, unless
you plan to have trigger support for foreign tables.
That is currently not supported, and I think that such
changes should be part of the present patch.

You are right. It might be unneeded change right now.
I'll revert it.

Other than that, I like the patch.
I cannot give an educated opinion if the changes to planner
and executor are well done or not.

Other comments:
---------------

I hope I find time to implement the API in oracle_fdw to
be able to test it more thoroughly.

There is an interdependence with the "FDW for PostgreSQL" patch
in the commitfest. If both these patches get committed, the
FDW should be extended to support the new API. If nothing else,
this would greatly improve the ability to test the new API
and find out if it is well designed.

It is the reason why I'd like to volunteer to review Hanada-san's patch.
I'll spent my time for reviewing his patch on this weekend.

Here is one other idea. My GPU acceleration module (PG-Strom)
implements column-oriented data store underlying foreign table.
It might make sense to cut out this portion for proof-of-concept of
writable foreign tables.

Any ideas?

The best would be to have a patch on top of the PostgreSQL FDW
to be able to test. It need not be perfect.

OK,

In addition, I noticed my patch didn't update documentation stuff.
I also add mention about new handlers.

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

#33Albe Laurenz
laurenz.albe@wien.gv.at
In reply to: Kohei KaiGai (#32)
Re: [v9.3] writable foreign tables

Kohei KaiGai wrote:

I am not so happy with GetForeignRelInfo:
- The name seems ill-chosen from the FDW API side.
I guess that you chose the name because the function
is called from get_relation_info, but I think the name
should be more descriptive for the FDW API.
Something like PlanForeignRelRowid.

Indeed, GetForeignRelInfo might give misleading impression
as if this routine collects widespread information about
target relation. So, how about GetForeignRelWidth() instead?

That would be better for the function as it is now.

- I guess that every FDW that only needs "rowid" will
do exactly the same as your fileGetForeignRelInfo.
Why can't that be done in core?
The function could pass an AttrNumber for the rowid
to the FDW, and will receive a boolean return code
depending on whether the FDW plans to use rowid or not.
That would be more convenient for FDW authors.

This design tries to kill two-birds with one-stone.
It enables to add multiple number of pseudo-columns,
not only "rowid", and makes possible to push-down
complex calculation of target list into external computing
resource.

For example, when user gives the following query:

SELECT ((c1 - c2) * (c2 - c3))^2 FROM ftable

it contains a complex calculation in the target-list,
thus, it also takes CPU cycles of local process.

If we can replace the "((c1 - c2) * (c2 - c3))^2" by
a reference to a pseudo-column that also references
the calculation result on external node, it effectively
off-load CPU cycles.

In this case, all we need to do is (1) acquire a slot
for pseudo-column at GetForeignRelInfo (2) replace
TargetEntry::expr by Var node that reference this
pseudo-column.

It makes sense for performance optimization, so I don't
want to restrict this handler for "rowid" only.

I understand.

But I think that you still can do that with the change that
I suggest. I suggest that GetForeignRelInfo (or whatever the
name ends up being) gets the AttrNumber of the proposed "rowid"
column in addition to the parameters you need for what
you want to do.

Then nothing would keep you from defining those
pseudo-columns. But all the setup necessary for the "rowid"
column could be moved out of the FDW. So for the 99% of all
FDW which are only interested in the rowid, things would
get much simpler and they don't all have to implement the
same code.

Did I make clear what I mean?
Would that be difficult?

- I guess the order is dictated by planner steps, but
it would be "nice to have" if GetForeignRelInfo were
not the first function to be called during planning.
That would make it easier for a FDW to support both
9.2 and 9.3 (fewer #ifdefs), because the FDW plan state
will probably have to be created in the first API
function.

The baserel->fdw_private should be initialized to NULL,
so it can perform as a mark whether private data is already
constructed, or not.

Right, if that pointer is pre-initialized to NULL, that
should work. Forget my quibble.

In addition, I noticed my patch didn't update documentation stuff.
I also add mention about new handlers.

I didn't get into documentation, comment and spelling issues since
the patch was still called POC, but yes, eventually that would
be necessary.

Yours,
Laurenz Albe

#34Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Albe Laurenz (#33)
Re: [v9.3] writable foreign tables

2012/11/19 Albe Laurenz <laurenz.albe@wien.gv.at>:

Kohei KaiGai wrote:

I am not so happy with GetForeignRelInfo:
- The name seems ill-chosen from the FDW API side.
I guess that you chose the name because the function
is called from get_relation_info, but I think the name
should be more descriptive for the FDW API.
Something like PlanForeignRelRowid.

Indeed, GetForeignRelInfo might give misleading impression
as if this routine collects widespread information about
target relation. So, how about GetForeignRelWidth() instead?

That would be better for the function as it is now.

- I guess that every FDW that only needs "rowid" will
do exactly the same as your fileGetForeignRelInfo.
Why can't that be done in core?
The function could pass an AttrNumber for the rowid
to the FDW, and will receive a boolean return code
depending on whether the FDW plans to use rowid or not.
That would be more convenient for FDW authors.

This design tries to kill two-birds with one-stone.
It enables to add multiple number of pseudo-columns,
not only "rowid", and makes possible to push-down
complex calculation of target list into external computing
resource.

For example, when user gives the following query:

SELECT ((c1 - c2) * (c2 - c3))^2 FROM ftable

it contains a complex calculation in the target-list,
thus, it also takes CPU cycles of local process.

If we can replace the "((c1 - c2) * (c2 - c3))^2" by
a reference to a pseudo-column that also references
the calculation result on external node, it effectively
off-load CPU cycles.

In this case, all we need to do is (1) acquire a slot
for pseudo-column at GetForeignRelInfo (2) replace
TargetEntry::expr by Var node that reference this
pseudo-column.

It makes sense for performance optimization, so I don't
want to restrict this handler for "rowid" only.

I understand.

But I think that you still can do that with the change that
I suggest. I suggest that GetForeignRelInfo (or whatever the
name ends up being) gets the AttrNumber of the proposed "rowid"
column in addition to the parameters you need for what
you want to do.

Then nothing would keep you from defining those
pseudo-columns. But all the setup necessary for the "rowid"
column could be moved out of the FDW. So for the 99% of all
FDW which are only interested in the rowid, things would
get much simpler and they don't all have to implement the
same code.

Did I make clear what I mean?
Would that be difficult?

All we have to do at get_relation_info() to deal with pseudo-
columns (including alternatives of complex calculation, not
only "rowid") is just expansion of rel->max_attr.
So, if FDW is not interested in something except for "rowid",
it can just inform the caller "Yeah, we need just one slot for
a pseudo-column of rowid". Otherwise, it can return another
value to acquire the slot for arbitrary pseudo-column.
I don't think it is a problematic design.

However, I'm skeptical 99% of FDWs don't support target-list
push-down. At least, it was very desired feature when I had
a talk at PGconf.EU last month. :-)

So, if we rename it to GetForeignRelWidth, is it defined as
follows?

extern AttrNumber
GetForeignRelWidth(PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid,
bool inhparent,
List *targetList);

Right now, inhparent makes no sense because foreign table
does not support table inheritance, but it seems to me we
shall have this functionality near future.

- I guess the order is dictated by planner steps, but
it would be "nice to have" if GetForeignRelInfo were
not the first function to be called during planning.
That would make it easier for a FDW to support both
9.2 and 9.3 (fewer #ifdefs), because the FDW plan state
will probably have to be created in the first API
function.

The baserel->fdw_private should be initialized to NULL,
so it can perform as a mark whether private data is already
constructed, or not.

Right, if that pointer is pre-initialized to NULL, that
should work. Forget my quibble.

In addition, I noticed my patch didn't update documentation stuff.
I also add mention about new handlers.

I didn't get into documentation, comment and spelling issues since
the patch was still called POC, but yes, eventually that would
be necessary.

Yours,
Laurenz Albe

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

#35Albe Laurenz
laurenz.albe@wien.gv.at
In reply to: Kohei KaiGai (#34)
Re: [v9.3] writable foreign tables

Kohei KaiGai wrote:

This design tries to kill two-birds with one-stone.
It enables to add multiple number of pseudo-columns,
not only "rowid", and makes possible to push-down
complex calculation of target list into external computing
resource.

For example, when user gives the following query:

SELECT ((c1 - c2) * (c2 - c3))^2 FROM ftable

it contains a complex calculation in the target-list,
thus, it also takes CPU cycles of local process.

If we can replace the "((c1 - c2) * (c2 - c3))^2" by
a reference to a pseudo-column that also references
the calculation result on external node, it effectively
off-load CPU cycles.

In this case, all we need to do is (1) acquire a slot
for pseudo-column at GetForeignRelInfo (2) replace
TargetEntry::expr by Var node that reference this
pseudo-column.

It makes sense for performance optimization, so I don't
want to restrict this handler for "rowid" only.

I understand.

But I think that you still can do that with the change that
I suggest. I suggest that GetForeignRelInfo (or whatever the
name ends up being) gets the AttrNumber of the proposed "rowid"
column in addition to the parameters you need for what
you want to do.

Then nothing would keep you from defining those
pseudo-columns. But all the setup necessary for the "rowid"
column could be moved out of the FDW. So for the 99% of all
FDW which are only interested in the rowid, things would
get much simpler and they don't all have to implement the
same code.

All we have to do at get_relation_info() to deal with pseudo-
columns (including alternatives of complex calculation, not
only "rowid") is just expansion of rel->max_attr.
So, if FDW is not interested in something except for "rowid",
it can just inform the caller "Yeah, we need just one slot for
a pseudo-column of rowid". Otherwise, it can return another
value to acquire the slot for arbitrary pseudo-column.
I don't think it is a problematic design.

However, I'm skeptical 99% of FDWs don't support target-list
push-down. At least, it was very desired feature when I had
a talk at PGconf.EU last month. :-)

I agree that PostgreSQL should make this technique possible.

My idea should not make this any more difficult.

So, if we rename it to GetForeignRelWidth, is it defined as
follows?

extern AttrNumber
GetForeignRelWidth(PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid,
bool inhparent,
List *targetList);

Right now, inhparent makes no sense because foreign table
does not support table inheritance, but it seems to me we
shall have this functionality near future.

I am thinking of this declaration:

extern bool
GetForeignRelWidth(PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid,
bool inhparent,
List *targetList,
AttrNumber rowidAttr);

Let me illustrate my idea with some code.

Here's your fileGetForeignRelInfo:

static void
fileGetForeignRelInfo(PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid,
bool inhparent, List *targetList)
{
FileFdwPlanState *fdw_private;
ListCell *cell;

fdw_private = (FileFdwPlanState *)
palloc0(sizeof(FileFdwPlanState));

foreach(cell, targetList)
{
TargetEntry *tle = lfirst(cell);

if (tle->resjunk &&
tle->resname && strcmp(tle->resname, "rowid")==0)
{
Bitmapset *temp = NULL;
AttrNumber anum_rowid;
DefElem *defel;

pull_varattnos((Node *)tle, baserel->relid, &temp);
anum_rowid = bms_singleton_member(temp)
+ FirstLowInvalidHeapAttributeNumber;
/* adjust attr_needed of baserel */
if (anum_rowid > baserel->max_attr)
baserel->max_attr = anum_rowid;
defel = makeDefElem("anum_rowid",
(Node *)makeInteger(anum_rowid));
fdw_private->anum_rowid = defel;
}
}
baserel->fdw_private = fdw_private;
}

I hope that this can be reduced to:

static bool
fileGetForeignRelRowid(PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid,
bool inhparent,
List *targetList,
AttrNumber rowidAttr)
{
FileFdwPlanState *fdw_private;
fdw_private = (FileFdwPlanState *)
palloc0(sizeof(FileFdwPlanState));

defel = makeDefElem("anum_rowid",
(Node *)makeInteger(rowidAttr));
fdw_private->anum_rowid = defel;

baserel->fdw_private = fdw_private;

return true; /* we'll use rowid, so please extend baserel->max_attr
*/
}

That wouldn't mean that the FDW cannot define any other
pseudo-columns in this function, just the case of rowid
would be simplified.

Does that make any sense?

Yours,
Laurenz Albe

#36Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Albe Laurenz (#35)
Re: [v9.3] writable foreign tables

2012/11/20 Albe Laurenz <laurenz.albe@wien.gv.at>:

Kohei KaiGai wrote:

This design tries to kill two-birds with one-stone.
It enables to add multiple number of pseudo-columns,
not only "rowid", and makes possible to push-down
complex calculation of target list into external computing
resource.

For example, when user gives the following query:

SELECT ((c1 - c2) * (c2 - c3))^2 FROM ftable

it contains a complex calculation in the target-list,
thus, it also takes CPU cycles of local process.

If we can replace the "((c1 - c2) * (c2 - c3))^2" by
a reference to a pseudo-column that also references
the calculation result on external node, it effectively
off-load CPU cycles.

In this case, all we need to do is (1) acquire a slot
for pseudo-column at GetForeignRelInfo (2) replace
TargetEntry::expr by Var node that reference this
pseudo-column.

It makes sense for performance optimization, so I don't
want to restrict this handler for "rowid" only.

I understand.

But I think that you still can do that with the change that
I suggest. I suggest that GetForeignRelInfo (or whatever the
name ends up being) gets the AttrNumber of the proposed "rowid"
column in addition to the parameters you need for what
you want to do.

Then nothing would keep you from defining those
pseudo-columns. But all the setup necessary for the "rowid"
column could be moved out of the FDW. So for the 99% of all
FDW which are only interested in the rowid, things would
get much simpler and they don't all have to implement the
same code.

All we have to do at get_relation_info() to deal with pseudo-
columns (including alternatives of complex calculation, not
only "rowid") is just expansion of rel->max_attr.
So, if FDW is not interested in something except for "rowid",
it can just inform the caller "Yeah, we need just one slot for
a pseudo-column of rowid". Otherwise, it can return another
value to acquire the slot for arbitrary pseudo-column.
I don't think it is a problematic design.

However, I'm skeptical 99% of FDWs don't support target-list
push-down. At least, it was very desired feature when I had
a talk at PGconf.EU last month. :-)

I agree that PostgreSQL should make this technique possible.

My idea should not make this any more difficult.

So, if we rename it to GetForeignRelWidth, is it defined as
follows?

extern AttrNumber
GetForeignRelWidth(PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid,
bool inhparent,
List *targetList);

Right now, inhparent makes no sense because foreign table
does not support table inheritance, but it seems to me we
shall have this functionality near future.

I am thinking of this declaration:

extern bool
GetForeignRelWidth(PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid,
bool inhparent,
List *targetList,
AttrNumber rowidAttr);

Let me illustrate my idea with some code.

Here's your fileGetForeignRelInfo:

static void
fileGetForeignRelInfo(PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid,
bool inhparent, List *targetList)
{
FileFdwPlanState *fdw_private;
ListCell *cell;

fdw_private = (FileFdwPlanState *)
palloc0(sizeof(FileFdwPlanState));

foreach(cell, targetList)
{
TargetEntry *tle = lfirst(cell);

if (tle->resjunk &&
tle->resname && strcmp(tle->resname, "rowid")==0)
{
Bitmapset *temp = NULL;
AttrNumber anum_rowid;
DefElem *defel;

pull_varattnos((Node *)tle, baserel->relid, &temp);
anum_rowid = bms_singleton_member(temp)
+ FirstLowInvalidHeapAttributeNumber;
/* adjust attr_needed of baserel */
if (anum_rowid > baserel->max_attr)
baserel->max_attr = anum_rowid;
defel = makeDefElem("anum_rowid",
(Node *)makeInteger(anum_rowid));
fdw_private->anum_rowid = defel;
}
}
baserel->fdw_private = fdw_private;
}

I hope that this can be reduced to:

static bool
fileGetForeignRelRowid(PlannerInfo *root,
RelOptInfo *baserel,
Oid foreigntableid,
bool inhparent,
List *targetList,
AttrNumber rowidAttr)
{
FileFdwPlanState *fdw_private;
fdw_private = (FileFdwPlanState *)
palloc0(sizeof(FileFdwPlanState));

defel = makeDefElem("anum_rowid",
(Node *)makeInteger(rowidAttr));
fdw_private->anum_rowid = defel;

baserel->fdw_private = fdw_private;

return true; /* we'll use rowid, so please extend baserel->max_attr
*/
}

That wouldn't mean that the FDW cannot define any other
pseudo-columns in this function, just the case of rowid
would be simplified.

Does that make any sense?

I think "bool" is not suitable data type to inform how many pseudo-columns
are needed for this scan on foreign-table.

Probably, it is helpful to provide a helper function that fetches an attribute-
number of pseudo "rowid" column from the supplied targetlist.
If we have GetPseudoRowidColumn() at foreign/foreign.c, the avove
routine can be rewritten as:

static AttrNumber
fileGetForeignRelWidth(PlannerInfo *root,
RelOptInfo *baserel,
Relation foreignrel,
bool inhparent, List *targetList)
{
FileFdwPlanState *fdw_private;
AttrNumber nattrs = RelationGetNumberOfAttributes(foreignrel);
AttrNumber anum_rowid;

fdw_private = palloc0(sizeof(FileFdwPlanState));
anum_rowid = GetPseudoRowidColumn(..., targetList);
if (anum_rowid > 0)
{
Assert(anum_rowid > nattrs);
fdw_private->anum_rowid
= makeDefElem("anum_rowid", (Node *)makeInteger(anum_rowid));
nattrs = anum_rowid;
}
baserel->fdw_private = fdw_private;

return nattrs;
}

In case when FDW drive wants to push-down other target entry into foreign-
side, thus, it needs multiple pseudo-columns, it is decision of the extension.
In addition, it does not take API change in the future, if some more additional
pseudo-column is required by some other new features.

How about your opinion?

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

#37Albe Laurenz
laurenz.albe@wien.gv.at
In reply to: Kohei KaiGai (#36)
Re: [v9.3] writable foreign tables

Kohei KaiGai wrote:

Probably, it is helpful to provide a helper function that fetches an

attribute-

number of pseudo "rowid" column from the supplied targetlist.
If we have GetPseudoRowidColumn() at foreign/foreign.c, the avove
routine can be rewritten as:

static AttrNumber
fileGetForeignRelWidth(PlannerInfo *root,
RelOptInfo *baserel,
Relation foreignrel,
bool inhparent, List

*targetList)

{
FileFdwPlanState *fdw_private;
AttrNumber nattrs = RelationGetNumberOfAttributes(foreignrel);
AttrNumber anum_rowid;

fdw_private = palloc0(sizeof(FileFdwPlanState));
anum_rowid = GetPseudoRowidColumn(..., targetList);
if (anum_rowid > 0)
{
Assert(anum_rowid > nattrs);
fdw_private->anum_rowid
= makeDefElem("anum_rowid", (Node

*)makeInteger(anum_rowid));

nattrs = anum_rowid;
}
baserel->fdw_private = fdw_private;

return nattrs;
}

In case when FDW drive wants to push-down other target entry into

foreign-

side, thus, it needs multiple pseudo-columns, it is decision of the

extension.

In addition, it does not take API change in the future, if some more

additional

pseudo-column is required by some other new features.

How about your opinion?

I think that this is better than what I suggested.

Yours,
Laurenz Albe

#38Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Albe Laurenz (#37)
1 attachment(s)
Re: [v9.3] writable foreign tables

The attached patch is revised version.

One most difference from the previous version is, it constructed
PoC features on Hanada-san's latest postgres-fdw.v5.patch.
Yesh, it looks to me working fine on RDBMS backend also.

Even though the filename of this patch contains "poc" phrase,
I think it may be time to consider adoption of the core regarding
to the interface portion.
(Of course, postgres_fdw is still works in progress.)

Here is a few operation examples.

postgres=# CREATE FOREIGN TABLE tbl (a int, b text, c date) SERVER loopback;
CREATE FOREIGN TABLE
postgres=# SELECT * FROM tbl;
a | b | c
---+-----+------------
1 | aaa | 2012-12-01
2 | bbb | 2012-12-02
3 | ccc | 2012-12-03
4 | ddd | 2012-12-04
5 | eee | 2012-12-05
6 | fff | 2012-12-06
(6 rows)

postgres=# UPDATE tbl SET b = b || b WHERE a % 2 = 1;
UPDATE 3
postgres=# SELECT * FROM tbl ORDER BY a;
a | b | c
---+--------+------------
1 | aaaaaa | 2012-12-01
2 | bbb | 2012-12-02
3 | cccccc | 2012-12-03
4 | ddd | 2012-12-04
5 | eeeeee | 2012-12-05
6 | fff | 2012-12-06
(6 rows)

postgres=# INSERT INTO tbl VALUES (7,'ggg'),(8,'hhh');
INSERT 0 2
postgres=# SELECT * FROM tbl ORDER BY a;
a | b | c
---+--------+------------
1 | aaaaaa | 2012-12-01
2 | bbb | 2012-12-02
3 | cccccc | 2012-12-03
4 | ddd | 2012-12-04
5 | eeeeee | 2012-12-05
6 | fff | 2012-12-06
7 | ggg |
8 | hhh |
(8 rows)

postgres=# DELETE FROM tbl WHERE a % 2 = 0;
DELETE 4
postgres=# SELECT * FROM tbl ORDER BY a;
a | b | c
---+--------+------------
1 | aaaaaa | 2012-12-01
3 | cccccc | 2012-12-03
5 | eeeeee | 2012-12-05
7 | ggg |
(4 rows)

Even though it still has restriction of transaction control on remote side,
I believe Hanada-san will provide more graceful implementation next to
the first read-only version getting committed.

So, let's back to the main topic of this patch.
According to the upthread discussion, I renamed the interface to inform
expected width of result set as follows:

+typedef AttrNumber (*GetForeignRelWidth_function) (PlannerInfo *root,
+                                                  RelOptInfo *baserel,
+                                                  Relation foreignrel,
+                                                  bool inhparent,
+                                                  List *targetList);

It informs the core how many slots for regular and pseudo columns shall
be acquired. If it is identical with number of attributed in foreign table
definition, it also means this scan does not use any pseudo columns.
A typical use case of pseudo column is "rowid" to move an identifier of
remote row from scan stage to modify stage. It is responsibility of FDW
driver to ensure "rowid" has uniqueness on the remote side; my
enhancement on postgres_fdw uses ctid.

get_pseudo_rowid_column() is a utility function that picks up an attribute
number of pseudo "rowid" column if query rewriter injected on previous
stage. If FDW does not support any other pseudo column features, the
value to be returned is just return-value of this function.

Other relevant APIs are as follows:

+typedef List *(*PlanForeignModify_function) (PlannerInfo *root,
+                                            ModifyTable *plan,
+                                            Index resultRelation,
+                                            Plan *subplan);
+
I newly added this handler on construction of ModifyTable structure.
Because INSERT command does not have scan stage directly connected
with table modification, FDW driver has no chance to construct its private
stuff relevant to table modification. (In case postgres_fdw, it constructs
the second query to modify remote table with/without given ctid.)
Its returned List * value is moved to BeginForeignModify handler as
third argument.
+typedef void (*BeginForeignModify_function) (ModifyTableState *mtstate,
+                                            ResultRelInfo *resultRelInfo,
+                                            List *fdw_private,
+                                            Plan *subplan,
+                                            int eflags);
I adjusted some arguments to reference fdw_private being constructed
on query plan stage. The role of this handler is not changed. FDW driver
should have all the initialization stuff on this handler, like we are doing at
BeginForeignScan.
+typedef int    (*ExecForeignInsert_function) (ResultRelInfo *resultRelInfo,
+                                          HeapTuple tuple);
+typedef int    (*ExecForeignDelete_function) (ResultRelInfo *resultRelInfo,
+                                          Datum rowid);
+typedef int    (*ExecForeignUpdate_function) (ResultRelInfo *resultRelInfo,
+                                          Datum rowid,
+                                          HeapTuple tuple);
These are not changed.

+typedef void (*EndForeignModify_function) (ResultRelInfo *resultRelInfo);

Also, it is not changed.

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

Attachments:

pgsql-v9.3-writable-fdw-poc.v5.patchapplication/octet-stream; name=pgsql-v9.3-writable-fdw-poc.v5.patchDownload
 contrib/postgres_fdw/connection.c              |   70 +-
 contrib/postgres_fdw/connection.h              |    8 +-
 contrib/postgres_fdw/deparse.c                 |  126 ++-
 contrib/postgres_fdw/expected/postgres_fdw.out | 1153 ++++++++++++++++++++++++
 contrib/postgres_fdw/postgres_fdw.c            |  405 ++++++++-
 contrib/postgres_fdw/postgres_fdw.h            |    6 +-
 contrib/postgres_fdw/sql/postgres_fdw.sql      |   10 +
 doc/src/sgml/fdwhandler.sgml                   |  152 +++-
 src/backend/executor/execMain.c                |   34 +-
 src/backend/executor/nodeForeignscan.c         |  136 ++-
 src/backend/executor/nodeModifyTable.c         |  150 ++-
 src/backend/foreign/foreign.c                  |   34 +
 src/backend/nodes/copyfuncs.c                  |    2 +
 src/backend/nodes/outfuncs.c                   |    2 +
 src/backend/optimizer/plan/createplan.c        |   48 +-
 src/backend/optimizer/plan/initsplan.c         |   10 +-
 src/backend/optimizer/plan/planmain.c          |    2 +-
 src/backend/optimizer/plan/planner.c           |    8 +-
 src/backend/optimizer/prep/prepunion.c         |    3 +-
 src/backend/optimizer/util/plancat.c           |   27 +-
 src/backend/optimizer/util/relnode.c           |    7 +-
 src/backend/rewrite/rewriteHandler.c           |   55 +-
 src/include/foreign/fdwapi.h                   |   31 +
 src/include/foreign/foreign.h                  |    3 +
 src/include/nodes/execnodes.h                  |   13 +-
 src/include/nodes/plannodes.h                  |    2 +
 src/include/optimizer/pathnode.h               |    2 +-
 src/include/optimizer/plancat.h                |    2 +-
 src/include/optimizer/planmain.h               |    6 +-
 29 files changed, 2413 insertions(+), 94 deletions(-)

diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index eab8b87..9ca7fbf 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -43,7 +43,7 @@ typedef struct ConnCacheEntry
 	Oid				serverid;	/* oid of foreign server */
 	Oid				userid;		/* oid of local user */
 
-	bool			use_tx;		/* true when using remote transaction */
+	int				conntx;		/* one of PGSQL_FDW_CONNTX_* */
 	int				refs;		/* reference counter */
 	PGconn		   *conn;		/* foreign server connection */
 } ConnCacheEntry;
@@ -65,6 +65,8 @@ cleanup_connection(ResourceReleasePhase phase,
 static PGconn *connect_pg_server(ForeignServer *server, UserMapping *user);
 static void begin_remote_tx(PGconn *conn);
 static void abort_remote_tx(PGconn *conn);
+static void commit_remote_tx(PGconn *conn);
+static void deallocate_remote_prepare(PGconn *conn);
 
 /*
  * Get a PGconn which can be used to execute foreign query on the remote
@@ -80,7 +82,7 @@ static void abort_remote_tx(PGconn *conn);
  * FDW object to invalidate already established connections.
  */
 PGconn *
-GetConnection(ForeignServer *server, UserMapping *user, bool use_tx)
+GetConnection(ForeignServer *server, UserMapping *user, int conntx)
 {
 	bool			found;
 	ConnCacheEntry *entry;
@@ -126,7 +128,7 @@ GetConnection(ForeignServer *server, UserMapping *user, bool use_tx)
 	entry = hash_search(ConnectionHash, &key, HASH_ENTER, &found);
 	if (!found)
 	{
-		entry->use_tx = false;
+		entry->conntx = PGSQL_FDW_CONNTX_NONE;
 		entry->refs = 0;
 		entry->conn = NULL;
 	}
@@ -162,7 +164,7 @@ GetConnection(ForeignServer *server, UserMapping *user, bool use_tx)
 		{
 			/* Clear connection cache entry on error case. */
 			PQfinish(entry->conn);
-			entry->use_tx = false;
+			entry->conntx = PGSQL_FDW_CONNTX_NONE;
 			entry->refs = 0;
 			entry->conn = NULL;
 			PG_RE_THROW();
@@ -182,10 +184,11 @@ GetConnection(ForeignServer *server, UserMapping *user, bool use_tx)
 	 * are in.  We need to remember whether this connection uses remote
 	 * transaction to abort it when this connection is released completely.
 	 */
-	if (use_tx && !entry->use_tx)
+	if (conntx > entry->conntx)
 	{
-		begin_remote_tx(entry->conn);
-		entry->use_tx = use_tx;
+		if (entry->conntx == PGSQL_FDW_CONNTX_NONE)
+			begin_remote_tx(entry->conn);
+		entry->conntx = conntx;
 	}
 
 	return entry->conn;
@@ -355,12 +358,45 @@ abort_remote_tx(PGconn *conn)
 	PQclear(res);
 }
 
+static void
+commit_remote_tx(PGconn *conn)
+{
+	PGresult	   *res;
+
+	elog(DEBUG3, "committing remote transaction");
+
+	res = PQexec(conn, "COMMIT TRANSACTION");
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		PQclear(res);
+		elog(ERROR, "could not commit transaction: %s", PQerrorMessage(conn));
+	}
+	PQclear(res);
+}
+
+static void
+deallocate_remote_prepare(PGconn *conn)
+{
+	PGresult	   *res;
+
+	elog(DEBUG3, "deallocating remote prepares");
+
+	res = PQexec(conn, "DEALLOCATE PREPARE ALL");
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		PQclear(res);
+		elog(ERROR, "could not deallocate prepared statement: %s",
+			 PQerrorMessage(conn));
+	}
+	PQclear(res);
+}
+
 /*
  * Mark the connection as "unused", and close it if the caller was the last
  * user of the connection.
  */
 void
-ReleaseConnection(PGconn *conn)
+ReleaseConnection(PGconn *conn, bool is_abort)
 {
 	HASH_SEQ_STATUS		scan;
 	ConnCacheEntry	   *entry;
@@ -412,7 +448,7 @@ ReleaseConnection(PGconn *conn)
 			 PQtransactionStatus(conn) == PQTRANS_INERROR ? "INERROR" :
 			 "UNKNOWN");
 		PQfinish(conn);
-		entry->use_tx = false;
+		entry->conntx = PGSQL_FDW_CONNTX_NONE;
 		entry->refs = 0;
 		entry->conn = NULL;
 		return;
@@ -430,10 +466,16 @@ ReleaseConnection(PGconn *conn)
 	 * If this connection uses remote transaction and there is no user other
 	 * than the caller, abort the remote transaction and forget about it.
 	 */
-	if (entry->use_tx && entry->refs == 0)
+	if (entry->conntx > PGSQL_FDW_CONNTX_NONE && entry->refs == 0)
 	{
-		abort_remote_tx(conn);
-		entry->use_tx = false;
+		if (entry->conntx > PGSQL_FDW_CONNTX_READ_ONLY)
+			deallocate_remote_prepare(conn);
+		if (is_abort || entry->conntx == PGSQL_FDW_CONNTX_READ_ONLY)
+			abort_remote_tx(conn);
+		else
+			commit_remote_tx(conn);
+
+		entry->conntx = PGSQL_FDW_CONNTX_NONE;
 	}
 }
 
@@ -485,7 +527,7 @@ cleanup_connection(ResourceReleasePhase phase,
 		elog(DEBUG3, "discard postgres_fdw connection %p due to resowner cleanup",
 			 entry->conn);
 		PQfinish(entry->conn);
-		entry->use_tx = false;
+		entry->conntx = PGSQL_FDW_CONNTX_NONE;
 		entry->refs = 0;
 		entry->conn = NULL;
 	}
@@ -597,7 +639,7 @@ postgres_fdw_disconnect(PG_FUNCTION_ARGS)
 
 	/* Discard cached connection, and clear reference counter. */
 	PQfinish(entry->conn);
-	entry->use_tx = false;
+	entry->conntx = PGSQL_FDW_CONNTX_NONE;
 	entry->refs = 0;
 	entry->conn = NULL;
 
diff --git a/contrib/postgres_fdw/connection.h b/contrib/postgres_fdw/connection.h
index 4c9d850..f97cc8a 100644
--- a/contrib/postgres_fdw/connection.h
+++ b/contrib/postgres_fdw/connection.h
@@ -16,10 +16,14 @@
 #include "foreign/foreign.h"
 #include "libpq-fe.h"
 
+#define PGSQL_FDW_CONNTX_NONE			0
+#define PGSQL_FDW_CONNTX_READ_ONLY		1
+#define PGSQL_FDW_CONNTX_READ_WRITE		2
+
 /*
  * Connection management
  */
-PGconn *GetConnection(ForeignServer *server, UserMapping *user, bool use_tx);
-void ReleaseConnection(PGconn *conn);
+PGconn *GetConnection(ForeignServer *server, UserMapping *user, int conntx);
+void ReleaseConnection(PGconn *conn, bool is_abort);
 
 #endif /* CONNECTION_H */
diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
index 69e6a3e..9e09429 100644
--- a/contrib/postgres_fdw/deparse.c
+++ b/contrib/postgres_fdw/deparse.c
@@ -12,6 +12,7 @@
  */
 #include "postgres.h"
 
+#include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/transam.h"
 #include "catalog/pg_class.h"
@@ -86,9 +87,11 @@ void
 deparseSimpleSql(StringInfo buf,
 				 PlannerInfo *root,
 				 RelOptInfo *baserel,
-				 List *local_conds)
+				 List *local_conds,
+				 AttrNumber anum_rowid)
 {
 	RangeTblEntry *rte;
+	Relation	rel;
 	ListCell   *lc;
 	StringInfoData	foreign_relname;
 	bool		first;
@@ -125,6 +128,24 @@ deparseSimpleSql(StringInfo buf,
 	}
 
 	/*
+	 * XXX - When this foreign table is target relation and RETURNING
+	 * clause reference some column, we have to mark these columns as
+	 * in-use. It is needed to support DELETE command, because INSERT
+	 * and UPDATE implicitly add references to all the regular columns
+	 * on baserel->reltargetlist.
+	 */
+	if (root->parse->resultRelation == baserel->relid &&
+		root->parse->returningList)
+	{
+		List   *attrs;
+
+		attrs = pull_var_clause((Node *) root->parse->returningList,
+								PVC_RECURSE_AGGREGATES,
+                                PVC_RECURSE_PLACEHOLDERS);
+		attr_used = list_union(attr_used, attrs);
+	}
+
+	/*
 	 * deparse SELECT clause
 	 *
 	 * List attributes which are in either target list or local restriction.
@@ -136,9 +157,10 @@ deparseSimpleSql(StringInfo buf,
 	 */
 	appendStringInfo(buf, "SELECT ");
 	rte = root->simple_rte_array[baserel->relid];
+	rel = heap_open(rte->relid, NoLock);
 	attr_used = list_union(attr_used, baserel->reltargetlist);
 	first = true;
-	for (attr = 1; attr <= baserel->max_attr; attr++)
+	for (attr = 1; attr <= RelationGetNumberOfAttributes(rel); attr++)
 	{
 		Var		   *var = NULL;
 		ListCell   *lc;
@@ -167,6 +189,10 @@ deparseSimpleSql(StringInfo buf,
 		else
 			appendStringInfo(buf, "NULL");
 	}
+	if (anum_rowid != InvalidAttrNumber)
+		appendStringInfo(buf, "%sctid", (first ? "" : ","));
+
+	heap_close(rel, NoLock);
 	appendStringInfoChar(buf, ' ');
 
 	/*
@@ -283,6 +309,102 @@ deparseAnalyzeSql(StringInfo buf, Relation rel)
 }
 
 /*
+ * deparse remote INSERT statement
+ */
+void
+deparseInsertSql(StringInfo buf, PlannerInfo *root, Index rtindex)
+{
+	RangeTblEntry  *rte = root->simple_rte_array[rtindex];
+	Relation		frel = heap_open(rte->relid, NoLock);
+	int				i, j, nattrs = RelationGetNumberOfAttributes(frel);
+	bool			is_first = true;
+
+	appendStringInfo(buf, "INSERT INTO ");
+	deparseRelation(buf, rte);
+	appendStringInfo(buf, "(");
+
+	for (i=0; i < nattrs; i++)
+	{
+		Form_pg_attribute attr = RelationGetDescr(frel)->attrs[i];
+		Var		var;
+
+		if (attr->attisdropped)
+			continue;
+
+		if (!is_first)
+			appendStringInfo(buf, ",");
+
+		var.varno = rtindex;
+		var.varattno = attr->attnum;
+		deparseVar(buf, &var, root);
+		is_first = false;
+	}
+	appendStringInfo(buf, ") VALUES (");
+
+	for (i=0, j=1; i < nattrs; i++)
+	{
+		Form_pg_attribute attr = RelationGetDescr(frel)->attrs[i];
+
+		if (attr->attisdropped)
+			continue;
+
+		appendStringInfo(buf, "%s$%d", (j == 1 ? "" : ","), j);
+		j++;
+	}
+	appendStringInfo(buf, ")");
+	heap_close(frel, NoLock);
+}
+
+/*
+ * deparse remote UPDATE statement
+ */
+void
+deparseUpdateSql(StringInfo buf, PlannerInfo *root, Index rtindex)
+{
+	RangeTblEntry  *rte = root->simple_rte_array[rtindex];
+	Relation		frel = heap_open(rte->relid, NoLock);
+	int				i, j, nattrs = RelationGetNumberOfAttributes(frel);
+	bool			is_first = true;
+
+	appendStringInfo(buf, "UPDATE ");
+	deparseRelation(buf, rte);
+	appendStringInfo(buf, " SET ");
+
+	for (i=0, j=2; i < nattrs; i++)
+	{
+		Form_pg_attribute attr = RelationGetDescr(frel)->attrs[i];
+		Var		var;
+
+		if (attr->attisdropped)
+			continue;
+
+		if (!is_first)
+			appendStringInfo(buf, ",");
+
+		var.varno = rtindex;
+		var.varattno = attr->attnum;
+		deparseVar(buf, &var, root);
+		appendStringInfo(buf, "=$%d", j++);
+		is_first = false;
+	}
+	appendStringInfo(buf, " WHERE ctid=$1");
+	heap_close(frel, NoLock);
+}
+
+/*
+ * deparse remote DELETE statement
+ */
+void
+deparseDeleteSql(StringInfo buf, PlannerInfo *root, Index rtindex)
+{
+	RangeTblEntry  *rte = root->simple_rte_array[rtindex];
+
+	appendStringInfo(buf, "DELETE FROM ");
+	deparseRelation(buf, rte);
+	appendStringInfo(buf, " WHERE ctid = $1");
+}
+
+/*
  * Deparse given expression into buf.  Actual string operation is delegated to
  * node-type-specific functions.
  *
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index f81c727..5ad00ea 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -736,6 +736,1159 @@ SELECT srvname FROM postgres_fdw_connections;
 (0 rows)
 
 -- ===================================================================
+-- test for writable foreign table stuff (PoC stage now)
+-- ===================================================================
+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)
+
+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)
+
+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 |                              |                          |    |            | 
+(102 rows)
+
+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
+    2 |   2 | 00002              | Sat Jan 03 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 |   9 | 00009              | 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
+   12 |   2 | 00012              | Tue Jan 13 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 |   9 | 00019              | 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
+   22 |   2 | 00022              | Fri Jan 23 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 |   9 | 00029              | 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
+   32 |   2 | 00032              | Mon Feb 02 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 |   9 | 00039              | 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
+   42 |   2 | 00042              | Thu Feb 12 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 |   9 | 00049              | 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
+   52 |   2 | 00052              | Sun Feb 22 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 |   9 | 00059              | 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
+   62 |   2 | 00062              | Wed Mar 04 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 |   9 | 00069              | 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
+   72 |   2 | 00072              | Sat Mar 14 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 |   9 | 00079              | 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
+   82 |   2 | 00082              | Tue Mar 24 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 |   9 | 00089              | 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
+   92 |   2 | 00092              | Fri Apr 03 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 |   9 | 00099              | 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
+  102 |   2 | 00102              | Sat Jan 03 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 |   9 | 00109              | 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
+  112 |   2 | 00112              | Tue Jan 13 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 |   9 | 00119              | 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
+  122 |   2 | 00122              | Fri Jan 23 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 |   9 | 00129              | 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
+  132 |   2 | 00132              | Mon Feb 02 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 |   9 | 00139              | 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
+  142 |   2 | 00142              | Thu Feb 12 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 |   9 | 00149              | 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
+  152 |   2 | 00152              | Sun Feb 22 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 |   9 | 00159              | 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
+  162 |   2 | 00162              | Wed Mar 04 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 |   9 | 00169              | 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
+  172 |   2 | 00172              | Sat Mar 14 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 |   9 | 00179              | 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
+  182 |   2 | 00182              | Tue Mar 24 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 |   9 | 00189              | 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
+  192 |   2 | 00192              | Fri Apr 03 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 |   9 | 00199              | 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
+  202 |   2 | 00202              | Sat Jan 03 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 |   9 | 00209              | 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
+  212 |   2 | 00212              | Tue Jan 13 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 |   9 | 00219              | 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
+  222 |   2 | 00222              | Fri Jan 23 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 |   9 | 00229              | 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
+  232 |   2 | 00232              | Mon Feb 02 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 |   9 | 00239              | 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
+  242 |   2 | 00242              | Thu Feb 12 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 |   9 | 00249              | 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
+  252 |   2 | 00252              | Sun Feb 22 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 |   9 | 00259              | 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
+  262 |   2 | 00262              | Wed Mar 04 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 |   9 | 00269              | 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
+  272 |   2 | 00272              | Sat Mar 14 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 |   9 | 00279              | 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
+  282 |   2 | 00282              | Tue Mar 24 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 |   9 | 00289              | 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
+  292 |   2 | 00292              | Fri Apr 03 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 |   9 | 00299              | 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
+  302 |   2 | 00302              | Sat Jan 03 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 |   9 | 00309              | 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
+  312 |   2 | 00312              | Tue Jan 13 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 |   9 | 00319              | 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
+  322 |   2 | 00322              | Fri Jan 23 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 |   9 | 00329              | 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
+  332 |   2 | 00332              | Mon Feb 02 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 |   9 | 00339              | 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
+  342 |   2 | 00342              | Thu Feb 12 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 |   9 | 00349              | 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
+  352 |   2 | 00352              | Sun Feb 22 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 |   9 | 00359              | 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
+  362 |   2 | 00362              | Wed Mar 04 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 |   9 | 00369              | 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
+  372 |   2 | 00372              | Sat Mar 14 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 |   9 | 00379              | 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
+  382 |   2 | 00382              | Tue Mar 24 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 |   9 | 00389              | 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
+  392 |   2 | 00392              | Fri Apr 03 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 |   9 | 00399              | 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
+  402 |   2 | 00402              | Sat Jan 03 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 |   9 | 00409              | 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
+  412 |   2 | 00412              | Tue Jan 13 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 |   9 | 00419              | 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
+  422 |   2 | 00422              | Fri Jan 23 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 |   9 | 00429              | 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
+  432 |   2 | 00432              | Mon Feb 02 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 |   9 | 00439              | 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
+  442 |   2 | 00442              | Thu Feb 12 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 |   9 | 00449              | 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
+  452 |   2 | 00452              | Sun Feb 22 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 |   9 | 00459              | 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
+  462 |   2 | 00462              | Wed Mar 04 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 |   9 | 00469              | 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
+  472 |   2 | 00472              | Sat Mar 14 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 |   9 | 00479              | 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
+  482 |   2 | 00482              | Tue Mar 24 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 |   9 | 00489              | 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
+  492 |   2 | 00492              | Fri Apr 03 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 |   9 | 00499              | 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
+  502 |   2 | 00502              | Sat Jan 03 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 |   9 | 00509              | 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
+  512 |   2 | 00512              | Tue Jan 13 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 |   9 | 00519              | 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
+  522 |   2 | 00522              | Fri Jan 23 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 |   9 | 00529              | 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
+  532 |   2 | 00532              | Mon Feb 02 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 |   9 | 00539              | 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
+  542 |   2 | 00542              | Thu Feb 12 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 |   9 | 00549              | 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
+  552 |   2 | 00552              | Sun Feb 22 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 |   9 | 00559              | 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
+  562 |   2 | 00562              | Wed Mar 04 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 |   9 | 00569              | 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
+  572 |   2 | 00572              | Sat Mar 14 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 |   9 | 00579              | 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
+  582 |   2 | 00582              | Tue Mar 24 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 |   9 | 00589              | 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
+  592 |   2 | 00592              | Fri Apr 03 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 |   9 | 00599              | 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
+  602 |   2 | 00602              | Sat Jan 03 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 |   9 | 00609              | 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
+  612 |   2 | 00612              | Tue Jan 13 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 |   9 | 00619              | 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
+  622 |   2 | 00622              | Fri Jan 23 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 |   9 | 00629              | 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
+  632 |   2 | 00632              | Mon Feb 02 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 |   9 | 00639              | 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
+  642 |   2 | 00642              | Thu Feb 12 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 |   9 | 00649              | 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
+  652 |   2 | 00652              | Sun Feb 22 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 |   9 | 00659              | 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
+  662 |   2 | 00662              | Wed Mar 04 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 |   9 | 00669              | 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
+  672 |   2 | 00672              | Sat Mar 14 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 |   9 | 00679              | 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
+  682 |   2 | 00682              | Tue Mar 24 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 |   9 | 00689              | 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
+  692 |   2 | 00692              | Fri Apr 03 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 |   9 | 00699              | 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
+  702 |   2 | 00702              | Sat Jan 03 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 |   9 | 00709              | 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
+  712 |   2 | 00712              | Tue Jan 13 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 |   9 | 00719              | 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
+  722 |   2 | 00722              | Fri Jan 23 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 |   9 | 00729              | 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
+  732 |   2 | 00732              | Mon Feb 02 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 |   9 | 00739              | 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
+  742 |   2 | 00742              | Thu Feb 12 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 |   9 | 00749              | 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
+  752 |   2 | 00752              | Sun Feb 22 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 |   9 | 00759              | 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
+  762 |   2 | 00762              | Wed Mar 04 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 |   9 | 00769              | 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
+  772 |   2 | 00772              | Sat Mar 14 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 |   9 | 00779              | 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
+  782 |   2 | 00782              | Tue Mar 24 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 |   9 | 00789              | 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
+  792 |   2 | 00792              | Fri Apr 03 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 |   9 | 00799              | 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
+  802 |   2 | 00802              | Sat Jan 03 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 |   9 | 00809              | 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
+  812 |   2 | 00812              | Tue Jan 13 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 |   9 | 00819              | 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
+  822 |   2 | 00822              | Fri Jan 23 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 |   9 | 00829              | 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
+  832 |   2 | 00832              | Mon Feb 02 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 |   9 | 00839              | 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
+  842 |   2 | 00842              | Thu Feb 12 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 |   9 | 00849              | 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
+  852 |   2 | 00852              | Sun Feb 22 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 |   9 | 00859              | 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
+  862 |   2 | 00862              | Wed Mar 04 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 |   9 | 00869              | 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
+  872 |   2 | 00872              | Sat Mar 14 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 |   9 | 00879              | 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
+  882 |   2 | 00882              | Tue Mar 24 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 |   9 | 00889              | 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
+  892 |   2 | 00892              | Fri Apr 03 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 |   9 | 00899              | 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
+  902 |   2 | 00902              | Sat Jan 03 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 |   9 | 00909              | 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
+  912 |   2 | 00912              | Tue Jan 13 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 |   9 | 00919              | 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
+  922 |   2 | 00922              | Fri Jan 23 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 |   9 | 00929              | 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
+  932 |   2 | 00932              | Mon Feb 02 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 |   9 | 00939              | 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
+  942 |   2 | 00942              | Thu Feb 12 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 |   9 | 00949              | 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
+  952 |   2 | 00952              | Sun Feb 22 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 |   9 | 00959              | 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
+  962 |   2 | 00962              | Wed Mar 04 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 |   9 | 00969              | 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
+  972 |   2 | 00972              | Sat Mar 14 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 |   9 | 00979              | 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
+  982 |   2 | 00982              | Tue Mar 24 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 |   9 | 00989              | 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
+  992 |   2 | 00992              | Fri Apr 03 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 |   9 | 00999              | Fri Apr 10 00:00:00 1970 PST
+ 1000 |   0 | 01000              | Thu Jan 01 00:00:00 1970 PST
+ 1001 | 101 | 0000100001         | 
+ 1002 | 102 | 0000200002         | 
+ 1003 | 403 | 0000300003_update3 | 
+ 1004 | 104 | 0000400004         | 
+ 1006 | 106 | 0000600006         | 
+ 1007 | 507 | 0000700007_update7 | 
+ 1008 | 108 | 0000800008         | 
+ 1009 | 109 | 0000900009         | 
+ 1010 | 100 | 0001000010         | 
+ 1011 | 101 | 0001100011         | 
+ 1012 | 102 | 0001200012         | 
+ 1013 | 403 | 0001300013_update3 | 
+ 1014 | 104 | 0001400014         | 
+ 1016 | 106 | 0001600016         | 
+ 1017 | 507 | 0001700017_update7 | 
+ 1018 | 108 | 0001800018         | 
+ 1019 | 109 | 0001900019         | 
+ 1020 | 100 | 0002000020         | 
+ 1101 | 201 | aaa                | 
+ 1102 | 202 | bbb                | 
+ 1103 | 503 | ccc_update3        | 
+(921 rows)
+
+-- ===================================================================
 -- cleanup
 -- ===================================================================
 DROP OPERATOR === (int, int) CASCADE;
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 6b870ab..bdb2da8 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -59,6 +59,7 @@ typedef struct PostgresFdwPlanState {
 	List		   *param_conds;
 	List		   *local_conds;
 	int				width;			/* obtained by remote EXPLAIN */
+	AttrNumber		anum_rowid;
 
 	/* Cached catalog information. */
 	ForeignTable   *table;
@@ -150,6 +151,20 @@ typedef struct PostgresAnalyzeState
 } PostgresAnalyzeState;
 
 /*
+ * Describes a state of modify request for a foreign table
+ */
+typedef struct PostgresFdwModifyState
+{
+	PGconn	   *conn;
+	char	   *query;
+	char	   *p_name;
+	int			p_nums;
+	Oid		   *p_types;
+	FmgrInfo   *p_flinfo;
+	MemoryContext	es_query_cxt;
+} PostgresFdwModifyState;
+
+/*
  * SQL functions
  */
 extern Datum postgres_fdw_handler(PG_FUNCTION_ARGS);
@@ -158,6 +173,11 @@ PG_FUNCTION_INFO_V1(postgres_fdw_handler);
 /*
  * FDW callback routines
  */
+static AttrNumber postgresGetForeignRelWidth(PlannerInfo *root,
+											 RelOptInfo *baserel,
+											 Relation foreignrel,
+											 bool inhparent,
+											 List *targetList);
 static void postgresGetForeignRelSize(PlannerInfo *root,
 									  RelOptInfo *baserel,
 									  Oid foreigntableid);
@@ -179,6 +199,23 @@ static void postgresEndForeignScan(ForeignScanState *node);
 static bool postgresAnalyzeForeignTable(Relation relation,
 										AcquireSampleRowsFunc *func,
 										BlockNumber *totalpages);
+static List *postgresPlanForeignModify(PlannerInfo *root,
+									   ModifyTable *plan,
+									   Index resultRelation,
+									   Plan *subplan);
+static void postgresBeginForeignModify(ModifyTableState *mtstate,
+									   ResultRelInfo *resultRelInfo,
+									   List *fdw_private,
+									   Plan *subplan,
+									   int eflags);
+static int postgresExecForeignInsert(ResultRelInfo *resultRelInfo,
+									 HeapTuple tuple);
+static int postgresExecForeignDelete(ResultRelInfo *resultRelInfo,
+									 Datum rowid);
+static int postgresExecForeignUpdate(ResultRelInfo *resultRelInfo,
+									 Datum rowid,
+									 HeapTuple tuple);
+static void postgresEndForeignModify(ResultRelInfo *resultRelInfo);
 
 /*
  * Helper functions
@@ -231,6 +268,7 @@ postgres_fdw_handler(PG_FUNCTION_ARGS)
 	FdwRoutine	*routine = makeNode(FdwRoutine);
 
 	/* Required handler functions. */
+	routine->GetForeignRelWidth = postgresGetForeignRelWidth;
 	routine->GetForeignRelSize = postgresGetForeignRelSize;
 	routine->GetForeignPaths = postgresGetForeignPaths;
 	routine->GetForeignPlan = postgresGetForeignPlan;
@@ -239,6 +277,12 @@ postgres_fdw_handler(PG_FUNCTION_ARGS)
 	routine->IterateForeignScan = postgresIterateForeignScan;
 	routine->ReScanForeignScan = postgresReScanForeignScan;
 	routine->EndForeignScan = postgresEndForeignScan;
+	routine->PlanForeignModify = postgresPlanForeignModify;
+	routine->BeginForeignModify = postgresBeginForeignModify;
+	routine->ExecForeignInsert = postgresExecForeignInsert;
+	routine->ExecForeignDelete = postgresExecForeignDelete;
+	routine->ExecForeignUpdate = postgresExecForeignUpdate;
+	routine->EndForeignModify = postgresEndForeignModify;
 
 	/* Optional handler functions. */
 	routine->AnalyzeForeignTable = postgresAnalyzeForeignTable;
@@ -247,6 +291,29 @@ postgres_fdw_handler(PG_FUNCTION_ARGS)
 }
 
 /*
+ * postgresGetForeignRelWidth
+ *		Informs how many columns (including pseudo ones) are needed.
+ */
+static AttrNumber
+postgresGetForeignRelWidth(PlannerInfo *root,
+						   RelOptInfo *baserel,
+						   Relation foreignrel,
+						   bool inhparent,
+						   List *targetList)
+{
+	PostgresFdwPlanState *fpstate = palloc0(sizeof(PostgresFdwPlanState));
+
+	baserel->fdw_private = fpstate;
+
+	/* does rowid pseudo-column is required? */
+	fpstate->anum_rowid = get_pseudo_rowid_column(baserel, targetList);
+	if (fpstate->anum_rowid != InvalidAttrNumber)
+		return fpstate->anum_rowid;
+
+	return RelationGetNumberOfAttributes(foreignrel);
+}
+
+/*
  * postgresGetForeignRelSize
  *		Estimate # of rows and width of the result of the scan
  *
@@ -283,7 +350,7 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	 * We use PostgresFdwPlanState to pass various information to subsequent
 	 * functions.
 	 */
-	fpstate = palloc0(sizeof(PostgresFdwPlanState));
+	fpstate = baserel->fdw_private;
 	initStringInfo(&fpstate->sql);
 	sql = &fpstate->sql;
 
@@ -320,10 +387,9 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	 */
 	classifyConditions(root, baserel, &remote_conds, &param_conds,
 					   &local_conds);
-	deparseSimpleSql(sql, root, baserel, local_conds);
+	deparseSimpleSql(sql, root, baserel, local_conds, fpstate->anum_rowid);
 	if (list_length(remote_conds) > 0)
 		appendWhereClause(sql, true, remote_conds, root);
-	elog(DEBUG3, "Query SQL: %s", sql->data);
 
 	/*
 	 * If the table or the server is configured to use remote EXPLAIN, connect
@@ -337,10 +403,10 @@ postgresGetForeignRelSize(PlannerInfo *root,
 		PGconn		   *conn;
 
 		user = GetUserMapping(GetOuterUserId(), server->serverid);
-		conn = GetConnection(server, user, false);
+		conn = GetConnection(server, user, PGSQL_FDW_CONNTX_NONE);
 		get_remote_estimate(sql->data, conn, &rows, &width,
 							&startup_cost, &total_cost);
-		ReleaseConnection(conn);
+		ReleaseConnection(conn, false);
 
 		/*
 		 * Estimate selectivity of conditions which are not used in remote
@@ -391,7 +457,6 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	fpstate->width = width;
 	fpstate->table = table;
 	fpstate->server = server;
-	baserel->fdw_private = (void *) fpstate;
 }
 
 /*
@@ -592,7 +657,7 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 	table = GetForeignTable(relid);
 	server = GetForeignServer(table->serverid);
 	user = GetUserMapping(GetOuterUserId(), server->serverid);
-	conn = GetConnection(server, user, true);
+	conn = GetConnection(server, user, PGSQL_FDW_CONNTX_READ_ONLY);
 	festate->conn = conn;
 
 	/* Result will be filled in first Iterate call. */
@@ -724,7 +789,7 @@ postgresEndForeignScan(ForeignScanState *node)
 	 * end of the scan to make the lifespan of remote transaction same as the
 	 * local query.
 	 */
-	ReleaseConnection(festate->conn);
+	ReleaseConnection(festate->conn, false);
 	festate->conn = NULL;
 
 	/* Discard fetch results */
@@ -790,7 +855,7 @@ get_remote_estimate(const char *sql, PGconn *conn,
 		PQclear(res);
 
 		/* Release connection and let connection manager cleanup. */
-		ReleaseConnection(conn);
+		ReleaseConnection(conn, true);
 
 		PG_RE_THROW();
 	}
@@ -947,7 +1012,7 @@ execute_query(ForeignScanState *node)
 		PQclear(res);
 
 		/* Release connection and let connection manager cleanup. */
-		ReleaseConnection(conn);
+		ReleaseConnection(conn, true);
 
 		/* propagate error */
 		PG_RE_THROW();
@@ -1105,6 +1170,8 @@ postgres_fdw_error_callback(void *arg)
 
 	relname = get_rel_name(errpos->relid);
 	colname = get_attname(errpos->relid, errpos->cur_attno);
+	if (!colname)
+		colname = "pseudo-column";
 	errcontext("column %s of foreign table %s",
 			   quote_identifier(colname), quote_identifier(relname));
 }
@@ -1172,7 +1239,7 @@ postgresAcquireSampleRowsFunc(Relation relation, int elevel,
 	table = GetForeignTable(relation->rd_id);
 	server = GetForeignServer(table->serverid);
 	user = GetUserMapping(GetOuterUserId(), server->serverid);
-	conn = GetConnection(server, user, true);
+	conn = GetConnection(server, user, PGSQL_FDW_CONNTX_READ_ONLY);
 
 	/*
 	 * Acquire sample rows from the result set.
@@ -1239,13 +1306,13 @@ postgresAcquireSampleRowsFunc(Relation relation, int elevel,
 		PQclear(res);
 
 		/* Release connection and let connection manager cleanup. */
-		ReleaseConnection(conn);
+		ReleaseConnection(conn, true);
 
 		PG_RE_THROW();
 	}
 	PG_END_TRY();
 
-	ReleaseConnection(conn);
+	ReleaseConnection(conn, false);
 
 	/* We assume that we have no dead tuple. */
 	*totaldeadrows = 0.0;
@@ -1429,3 +1496,315 @@ analyze_row_processor(PGresult *res, PostgresAnalyzeState *astate, bool first)
 
 	return;
 }
+
+static List *
+postgresPlanForeignModify(PlannerInfo *root,
+						  ModifyTable *plan,
+						  Index resultRelaion,
+						  Plan *subplan)
+{
+	CmdType			operation = plan->operation;
+	StringInfoData	sql;
+
+	initStringInfo(&sql);
+
+	/*
+	 * XXX - In case of UPDATE or DELETE commands are quite "simple",
+	 * we will be able to execute raw UPDATE or DELETE statement at
+	 * the stage of scan, instead of combination SELECT ... FOR UPDATE
+	 * and either of UPDATE or DELETE commands.
+	 * It should be an idea of optimization in the future version.
+	 *
+	 * XXX - FOR UPDATE should be appended on the remote query of scan
+	 * stage to avoid unexpected concurrent update on the target rows.
+	 */
+	if (operation == CMD_UPDATE || operation == CMD_DELETE)
+	{
+		ForeignScan	   *fscan = (ForeignScan *) subplan;
+		Value		   *select_sql;
+
+		Assert(IsA(fscan, ForeignScan));
+		select_sql = list_nth(fscan->fdw_private,
+							  FdwPrivateSelectSql);
+		appendStringInfo(&sql, "%s FOR UPDATE", strVal(select_sql));
+		strVal(select_sql) = pstrdup(sql.data);
+
+		resetStringInfo(&sql);
+	}
+
+	switch (operation)
+	{
+		case CMD_INSERT:
+			deparseInsertSql(&sql, root, resultRelaion);
+			elog(DEBUG3, "Remote INSERT query: %s", sql.data);
+			break;
+		case CMD_UPDATE:
+			deparseUpdateSql(&sql, root, resultRelaion);
+			elog(DEBUG3, "Remote UPDATE query: %s", sql.data);
+			break;
+		case CMD_DELETE:
+			deparseDeleteSql(&sql, root, resultRelaion);
+			elog(DEBUG3, "Remote DELETE query: %s", sql.data);
+			break;
+		default:
+			elog(ERROR, "unexpected operation: %d", (int) operation);
+	}
+	return list_make1(makeString(sql.data));
+}
+
+static void
+postgresBeginForeignModify(ModifyTableState *mtstate,
+						   ResultRelInfo *resultRelInfo,
+						   List *fdw_private,
+						   Plan *subplan,
+						   int eflags)
+{
+	PostgresFdwModifyState *fmstate;
+	CmdType			operation = mtstate->operation;
+	Relation		frel = resultRelInfo->ri_RelationDesc;
+	AttrNumber		n_params = RelationGetNumberOfAttributes(frel) + 1;
+	ForeignTable   *ftable;
+	ForeignServer  *fserver;
+	UserMapping	   *fuser;
+	Oid				out_func_oid;
+	bool			isvarlena;
+
+	/*
+	 * Construct PostgresFdwExecutionState
+	 */
+	fmstate = palloc0(sizeof(PostgresFdwExecutionState));
+
+	ftable = GetForeignTable(RelationGetRelid(frel));
+	fserver = GetForeignServer(ftable->serverid);
+	fuser = GetUserMapping(GetOuterUserId(), fserver->serverid);
+
+	fmstate->query = strVal(linitial(fdw_private));
+	fmstate->conn = GetConnection(fserver, fuser,
+								  PGSQL_FDW_CONNTX_READ_WRITE);
+	fmstate->p_name = NULL;
+	fmstate->p_types = palloc0(sizeof(Oid) * n_params);
+	fmstate->p_flinfo = palloc0(sizeof(FmgrInfo) * n_params);
+
+	/* 1st parameter should be ctid on UPDATE or DELETE */
+	if (operation == CMD_UPDATE || operation == CMD_DELETE)
+	{
+		fmstate->p_types[fmstate->p_nums] = TIDOID;
+		getTypeOutputInfo(TIDOID, &out_func_oid, &isvarlena);
+		fmgr_info(out_func_oid, &fmstate->p_flinfo[fmstate->p_nums]);
+		fmstate->p_nums++;
+	}
+	/* following parameters should be regular columns */
+	if (operation == CMD_UPDATE || operation == CMD_INSERT)
+	{
+		AttrNumber	index;
+
+		for (index=0; index < RelationGetNumberOfAttributes(frel); index++)
+		{
+			Form_pg_attribute	attr = RelationGetDescr(frel)->attrs[index];
+
+			if (attr->attisdropped)
+				continue;
+
+			fmstate->p_types[fmstate->p_nums] = attr->atttypid;
+			getTypeOutputInfo(attr->atttypid, &out_func_oid, &isvarlena);
+			fmgr_info(out_func_oid, &fmstate->p_flinfo[fmstate->p_nums]);
+			fmstate->p_nums++;
+		}
+	}
+	Assert(fmstate->p_nums <= n_params);
+	fmstate->es_query_cxt = mtstate->ps.state->es_query_cxt;
+	resultRelInfo->ri_fdw_state = fmstate;
+}
+
+static void
+prepare_foreign_modify(PostgresFdwModifyState *fmstate)
+{
+	static int	prep_id = 1;
+	char		prep_name[NAMEDATALEN];
+	PGresult   *res;
+
+	snprintf(prep_name, sizeof(prep_name),
+			 "pgsql_fdw_prep_%08x", prep_id++);
+
+	res = PQprepare(fmstate->conn,
+					prep_name,
+					fmstate->query,
+					fmstate->p_nums,
+					fmstate->p_types);
+	if (!res || PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		PQclear(res);
+		elog(ERROR, "could not prepare statement (%s): %s",
+			 fmstate->query, PQerrorMessage(fmstate->conn));
+	}
+	PQclear(res);
+
+	fmstate->p_name = MemoryContextStrdup(fmstate->es_query_cxt, prep_name);
+}
+
+static int
+setup_exec_prepared(ResultRelInfo *resultRelInfo,
+					const char *rowid, HeapTuple tuple,
+					const char *p_values[], int p_lengths[])
+{
+	PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state;
+	Relation	frel = resultRelInfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(frel);
+	int			i, p_idx = 0;
+
+	/* 1st parameter should be ctid */
+	if (rowid)
+	{
+		p_values[p_idx] = rowid;
+		p_lengths[p_idx] = strlen(rowid) + 1;
+		p_idx++;
+	}
+
+	/* following parameters are as TupleDesc */
+	if (HeapTupleIsValid(tuple))
+	{
+		for (i = 0; i < tupdesc->natts; i++)
+		{
+			Form_pg_attribute	attr = tupdesc->attrs[i];
+			Datum		value;
+			bool		isnull;
+
+			if (attr->attisdropped)
+				continue;
+
+			value = heap_getattr(tuple, attr->attnum, tupdesc, &isnull);
+			if (isnull)
+			{
+				p_values[p_idx] = NULL;
+				p_lengths[p_idx] = 0;
+			}
+			else
+			{
+				p_values[p_idx] =
+					OutputFunctionCall(&fmstate->p_flinfo[p_idx], value);
+				p_lengths[p_idx] = strlen(p_values[p_idx]) + 1;
+			}
+			p_idx++;
+		}
+	}
+	return p_idx;
+}
+
+static int
+postgresExecForeignInsert(ResultRelInfo *resultRelInfo,
+						  HeapTuple tuple)
+{
+	PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state;
+	const char	  **p_values  = alloca(sizeof(char *) * fmstate->p_nums);
+	int			   *p_lengths = alloca(sizeof(int) * fmstate->p_nums);
+	AttrNumber		nattrs;
+	PGresult	   *res;
+	int				n_rows;
+
+	if (!fmstate->p_name)
+		prepare_foreign_modify(fmstate);
+
+	nattrs = setup_exec_prepared(resultRelInfo,
+								 NULL, tuple,
+								 p_values, p_lengths);
+	Assert(fmstate->p_nums == nattrs);
+
+	res = PQexecPrepared(fmstate->conn,
+						 fmstate->p_name,
+						 nattrs,
+						 p_values,
+						 p_lengths,
+						 NULL, 0);
+	if (!res ||  PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		PQclear(res);
+		elog(ERROR, "could not execute prepared statement (%s): %s",
+			 fmstate->query, PQerrorMessage(fmstate->conn));
+    }
+	n_rows = atoi(PQcmdTuples(res));
+    PQclear(res);
+
+	return n_rows;
+}
+
+static int
+postgresExecForeignDelete(ResultRelInfo *resultRelInfo, Datum rowid)
+{
+	PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state;
+	const char	   *p_values[1];
+	int				p_lengths[1];
+	AttrNumber		nattrs;
+	PGresult	   *res;
+	int				n_rows;
+
+	if (!fmstate->p_name)
+		prepare_foreign_modify(fmstate);
+
+	nattrs = setup_exec_prepared(resultRelInfo,
+								 DatumGetCString(rowid), NULL,
+								 p_values, p_lengths);
+	Assert(fmstate->p_nums == nattrs);
+
+	res = PQexecPrepared(fmstate->conn,
+						 fmstate->p_name,
+						 nattrs,
+						 p_values,
+						 p_lengths,
+						 NULL, 0);
+	if (!res ||  PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		PQclear(res);
+		elog(ERROR, "could not execute prepared statement (%s): %s",
+			 fmstate->query, PQerrorMessage(fmstate->conn));
+    }
+	n_rows = atoi(PQcmdTuples(res));
+    PQclear(res);
+
+	return n_rows;
+}
+
+static int
+postgresExecForeignUpdate(ResultRelInfo *resultRelInfo,
+						  Datum rowid, HeapTuple tuple)
+{
+	PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state;
+	const char	  **p_values  = alloca(sizeof(char *) * (fmstate->p_nums + 1));
+	int			   *p_lengths = alloca(sizeof(int) * (fmstate->p_nums + 1));
+	AttrNumber		nattrs;
+	PGresult	   *res;
+	int				n_rows;
+
+	if (!fmstate->p_name)
+		prepare_foreign_modify(fmstate);
+
+	nattrs = setup_exec_prepared(resultRelInfo,
+								 DatumGetCString(rowid), tuple,
+								 p_values, p_lengths);
+	Assert(fmstate->p_nums == nattrs);
+
+	res = PQexecPrepared(fmstate->conn,
+						 fmstate->p_name,
+						 nattrs,
+						 p_values,
+						 p_lengths,
+						 NULL, 0);
+	if (!res ||  PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		PQclear(res);
+		elog(ERROR, "could not execute prepared statement (%s): %s",
+			 fmstate->query, PQerrorMessage(fmstate->conn));
+    }
+	n_rows = atoi(PQcmdTuples(res));
+    PQclear(res);
+
+	return n_rows;
+}
+
+static void
+postgresEndForeignModify(ResultRelInfo *resultRelInfo)
+{
+	PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state;
+
+	ReleaseConnection(fmstate->conn, false);
+	fmstate->conn = NULL;
+}
diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h
index b5cefb8..3756a0d 100644
--- a/contrib/postgres_fdw/postgres_fdw.h
+++ b/contrib/postgres_fdw/postgres_fdw.h
@@ -30,7 +30,8 @@ int GetFetchCountOption(ForeignTable *table, ForeignServer *server);
 void deparseSimpleSql(StringInfo buf,
 					  PlannerInfo *root,
 					  RelOptInfo *baserel,
-					  List *local_conds);
+					  List *local_conds,
+					  AttrNumber anum_rowid);
 void appendWhereClause(StringInfo buf,
 					   bool has_where,
 					   List *exprs,
@@ -41,5 +42,8 @@ void classifyConditions(PlannerInfo *root,
 						List **param_conds,
 						List **local_conds);
 void deparseAnalyzeSql(StringInfo buf, Relation rel);
+void deparseInsertSql(StringInfo buf, PlannerInfo *root, Index rtindex);
+void deparseUpdateSql(StringInfo buf, PlannerInfo *root, Index rtindex);
+void deparseDeleteSql(StringInfo buf, PlannerInfo *root, Index rtindex);
 
 #endif /* POSTGRESQL_FDW_H */
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 7845e70..700d607 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -300,6 +300,16 @@ ERROR OUT;          -- ERROR
 SELECT srvname FROM postgres_fdw_connections;
 
 -- ===================================================================
+-- test for writable foreign table stuff (PoC stage now)
+-- ===================================================================
+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 *;
+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 *;
+DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING *;
+SELECT c1,c2,c3,c4 FROM ft2 ORDER BY c1;
+
+-- ===================================================================
 -- cleanup
 -- ===================================================================
 DROP OPERATOR === (int, int) CASCADE;
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index 2d604ed..a2ec974 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -89,6 +89,46 @@
 
     <para>
 <programlisting>
+AttrNumber
+GetForeignRelWidth(PlannerInfo *root,
+                   RelOptInfo *baserel,
+                   Relation foreignrel,
+                   bool inhparent,
+                   List *targetList);
+</programlisting>
+     Obtain width of the result set to be fetched on foreign table scan.
+     This is an optional handler, and called at the beginning of planning
+     for a query involving a foreign table; during construction of
+     <literal>RelOptInfo</>.
+     <literal>root</> is the planner's global information about the query;
+     <literal>baserel</> is the planner's information about this table under
+     construction; and <literal>foreignrel</> is <literal>Relation</>
+     descriptor of the foreign table.
+     <literal>inhparent</> is a boolean to show whether the relation is
+     inheritance parent, even though foreign table does not support table
+     inheritance right now. <literal>targetList</> is list of
+     <literal>TargetEntry</> to be returned from the (sub-)query being
+     currently focused on.
+    </para>
+
+    <para>
+     The result value of this function shall be assigned on the
+     <literal>baserel-&gt;max_attr</>; that means expected number of
+     columns being fetched on the foreign table scan.
+     It should not be smaller than number of regular columns in definition
+     of this foreign table. All we can do is expansion of this value to
+     acquire slots for some additional attributes; being called pseudo-
+     columns. A typical usage of pseudo-column is to move an identifier of
+     a particular remote row to be updated or deleted from scanning stage
+     to modifying stage when foreign table performs as result relation of
+     the query. In addition, it also allows off-load burden of complex
+     calculation into foreign computing resources, and replace it with
+     just a reference of its calculated result on the remote side, instead
+     of local calculation.
+   </para>
+
+    <para>
+<programlisting>
 void
 GetForeignRelSize (PlannerInfo *root,
                    RelOptInfo *baserel,
@@ -96,7 +136,8 @@ 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 involving a foreign table,
+     unless <literal>FixupGetForeignRelWidth</> is not configured.
      <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
@@ -315,6 +356,115 @@ AcquireSampleRowsFunc (Relation relation, int elevel,
     </para>
 
     <para>
+     If FDW driver supports writable foreign tables, it must implement both
+     of <literal>BeginForeignModify</> and <literal>EndForeignModify</> at
+     least. In addition, some or all of <literal>ExecForeignInsert</>,
+     <literal>ExecForeignUpdate</> and <literal>ExecForeignDelete</> need
+     to be implement according to capability of FDW.
+    </para>     
+
+    <para>
+<programlisting>
+List *
+PlanForeignModify(PlannerInfo *root,
+                  ModifyTable *plan,
+                  Index resultRelation,
+                  Plan *subplan);
+</programlisting>
+     It allows FDW drivers to construct its own private stuff relevant to
+     the foreign table to be modified. This private stuff should have
+     form of <literal>List *</> data, then it shall be delivered as
+     3rd argument of <literal>BeginForeignModify</> on execution stage.
+    </para>
+    <para>
+     <literal>root</> is the planner's global information about the query.
+     <literal>plan</> is the master plan to modify result relation according
+     to its command type. Please also pay attention <literal>ModifyTable</>
+     may have multiple result relations in the future revision, even though
+     we don't support inheritance feature on foreign tables.
+     <literal>resultRelation</> is an index towards result relation in the
+     range table entries, and <literal>subplan</> is the relevant scan plan;
+     that should be <literal>ForeignScan</> at <literal>UPDATE</> or
+     <literal>DELETE</>, so the driver can access private datum of scan stage
+     using this argument.
+    </para>
+
+    <para>
+<programlisting>
+void
+BeginForeignModify (ModifyTableState *mtstate,
+                    ResultRelInfo *resultRelInfo,
+                    List *fdw_private,
+                    Plan *subplan,
+                    int eflags);
+</programlisting>
+     It is invoked at beginning of foreign table modification, durting
+     execurot startup. This routine should perform any initialization
+     stuffs needed prior to actual table modifications, but not start
+     modifying the actual tuples. (That should be done for each call of
+     <function>ExecForeignInsert</>, <function>ExecForeignUpdate</> or
+     <function>ExecForeignDelete</>.)
+    </para>
+    <para>
+     The <structfield>ri_fdw_state</> field of <structname>ResultRelInfo</>
+     is reserved to store any private stuff relevant to this foreign table,
+     so it is assumed this routine handles any initialization stuff and
+     save its per-scan state on this field.
+     Please also pay attention <literal>ModifyTableState</> may have multiple
+     result relations in the future revision, even though we don't support
+     inheritance feature on foreign tables. <literal>resultRelInfo</> is
+     master information connected to this foreign table.
+     <literal>fdw_private</> is a private stuff constructed at
+     <literal>PlanForeignModify</>, and <literal>subplan</> is the relevant
+     scan plan of this table modification.
+    </para>
+
+    <para>
+<programlisting>
+int
+ExecForeignInsert (ResultRelInfo *resultRelInfo,
+                   HeapTuple tuple);
+</programlisting>
+     Insert the given tuple into backing storage behalf on the foreign table.
+     The supplied tuple is already formed according to the definition of
+     relation, thus, all the pseudo-columns are already filtered out.
+     It can return number of rows being inserted, usually 1.
+    </para>
+
+    <para>
+<programlisting>
+int
+ExecForeignDelete (ResultRelInfo *resultRelInfo,
+                   Datum rowid);
+</programlisting>
+     Delete the tuple being identified with <literal>rowid</> in the backing
+     storage behalf on the foreign table.
+     It can return number of rows being deleted, usually 1.
+    </para>
+
+    <para>
+<programlisting>
+int
+ExecForeignUpdate (ResultRelInfo *resultRelInfo,
+                   Datum rowid,
+                   HeapTuple tuple);
+</programlisting>
+     Update the tuple being identified with <literal>rowid</> in the backing
+     storage behalf on the foreign table, by the given tuple.
+     It can return number of rows being updated, usually 1.
+    </para>
+
+    <para>
+<programlisting>
+void
+EndForeignModify (ResultRelInfo *resultRelInfo);
+</programlisting>
+     End the modification 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>
      The <structname>FdwRoutine</> struct type is declared in
      <filename>src/include/foreign/fdwapi.h</>, which see for additional
      details.
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index dbd3755..ccc1f3a 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"
@@ -934,6 +935,7 @@ void
 CheckValidResultRel(Relation resultRel, CmdType operation)
 {
 	TriggerDesc *trigDesc = resultRel->trigdesc;
+	FdwRoutine	*fdwroutine;
 
 	switch (resultRel->rd_rel->relkind)
 	{
@@ -985,10 +987,34 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
 			}
 			break;
 		case RELKIND_FOREIGN_TABLE:
-			ereport(ERROR,
-					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("cannot change foreign table \"%s\"",
-							RelationGetRelationName(resultRel))));
+			fdwroutine = GetFdwRoutineByRelId(RelationGetRelid(resultRel));
+			switch (operation)
+			{
+				case CMD_INSERT:
+					if (!fdwroutine || !fdwroutine->ExecForeignInsert)
+						ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("cannot insert into foreign table \"%s\"",
+									RelationGetRelationName(resultRel))));
+					break;
+				case CMD_UPDATE:
+					if (!fdwroutine || !fdwroutine->ExecForeignUpdate)
+						ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("cannot update foreign table \"%s\"",
+									RelationGetRelationName(resultRel))));
+					break;
+				case CMD_DELETE:
+					if (!fdwroutine || !fdwroutine->ExecForeignDelete)
+						ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("cannot delete from foreign table \"%s\"",
+									RelationGetRelationName(resultRel))));
+					break;
+				default:
+					elog(ERROR, "unrecognized CmdType: %d", (int) operation);
+					break;
+			}
 			break;
 		default:
 			ereport(ERROR,
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 9204859..1ee626f 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -25,6 +25,7 @@
 #include "executor/executor.h"
 #include "executor/nodeForeignscan.h"
 #include "foreign/fdwapi.h"
+#include "nodes/nodeFuncs.h"
 #include "utils/rel.h"
 
 static TupleTableSlot *ForeignNext(ForeignScanState *node);
@@ -93,6 +94,133 @@ ExecForeignScan(ForeignScanState *node)
 					(ExecScanRecheckMtd) ForeignRecheck);
 }
 
+/*
+ * pseudo_column_walker
+ *
+ * helper routine of GetPseudoTupleDesc. It pulls Var nodes that reference
+ * pseudo columns from targetlis of the relation
+ */
+typedef struct
+{
+	Relation	relation;
+	Index		varno;
+	List	   *pcolumns;
+	AttrNumber	max_attno;
+} pseudo_column_walker_context;
+
+static bool
+pseudo_column_walker(Node *node, pseudo_column_walker_context *context)
+{
+	if (node == NULL)
+		return false;
+	if (IsA(node, Var))
+	{
+		Var		   *var = (Var *) node;
+		ListCell   *cell;
+
+		if (var->varno == context->varno && var->varlevelsup == 0 &&
+			var->varattno > RelationGetNumberOfAttributes(context->relation))
+		{
+			foreach (cell, context->pcolumns)
+			{
+				Var	   *temp = lfirst(cell);
+
+				if (temp->varattno == var->varattno)
+				{
+					if (!equal(var, temp))
+						elog(ERROR, "asymmetric pseudo column appeared");
+					break;
+				}
+			}
+			if (!cell)
+			{
+				context->pcolumns = lappend(context->pcolumns, var);
+				if (var->varattno > context->max_attno)
+					context->max_attno = var->varattno;
+			}
+		}
+		return false;
+	}
+
+	/* Should not find an unplanned subquery */
+	Assert(!IsA(node, Query));
+
+	return expression_tree_walker(node, pseudo_column_walker,
+								  (void *)context);
+}
+
+/*
+ * GetPseudoTupleDesc
+ *
+ * It generates TupleDesc structure including pseudo-columns if required.
+ */
+static TupleDesc
+GetPseudoTupleDesc(ForeignScan *node, Relation relation)
+{
+	pseudo_column_walker_context context;
+	List	   *target_list = node->scan.plan.targetlist;
+	TupleDesc	tupdesc;
+	AttrNumber	attno;
+	ListCell   *cell;
+	ListCell   *prev;
+	bool		hasoid;
+
+	context.relation = relation;
+	context.varno = node->scan.scanrelid;
+	context.pcolumns = NIL;
+	context.max_attno = -1;
+
+	pseudo_column_walker((Node *)target_list, (void *)&context);
+	Assert(context.max_attno > RelationGetNumberOfAttributes(relation));
+
+	hasoid = RelationGetForm(relation)->relhasoids;
+	tupdesc = CreateTemplateTupleDesc(context.max_attno, hasoid);
+
+	for (attno = 1; attno <= context.max_attno; attno++)
+	{
+		/* case of regular columns */
+		if (attno <= RelationGetNumberOfAttributes(relation))
+		{
+			memcpy(tupdesc->attrs[attno - 1],
+				   RelationGetDescr(relation)->attrs[attno - 1],
+				   ATTRIBUTE_FIXED_PART_SIZE);
+			continue;
+		}
+
+		/* case of pseudo columns */
+		prev = NULL;
+		foreach (cell, context.pcolumns)
+		{
+			Var	   *var = lfirst(cell);
+
+			if (var->varattno == attno)
+			{
+				char		namebuf[NAMEDATALEN];
+
+				snprintf(namebuf, sizeof(namebuf),
+						 "pseudo_column_%d", attno);
+
+				TupleDescInitEntry(tupdesc,
+								   attno,
+								   namebuf,
+								   var->vartype,
+								   var->vartypmod,
+								   0);
+				TupleDescInitEntryCollation(tupdesc,
+											attno,
+											var->varcollid);
+				context.pcolumns
+					= list_delete_cell(context.pcolumns, cell, prev);
+				break;
+			}
+			prev = cell;
+		}
+		if (!cell)
+			elog(ERROR, "pseudo column %d of %s not in target list",
+				 attno, RelationGetRelationName(relation));
+	}
+	return tupdesc;
+}
 
 /* ----------------------------------------------------------------
  *		ExecInitForeignScan
@@ -103,6 +231,7 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
 {
 	ForeignScanState *scanstate;
 	Relation	currentRelation;
+	TupleDesc	tupdesc;
 	FdwRoutine *fdwroutine;
 
 	/* check for unsupported flags */
@@ -149,7 +278,12 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
 	/*
 	 * get the scan type from the relation descriptor.
 	 */
-	ExecAssignScanType(&scanstate->ss, RelationGetDescr(currentRelation));
+	if (node->fsPseudoCol)
+		tupdesc = GetPseudoTupleDesc(node, currentRelation);
+	else
+		tupdesc = RelationGetDescr(currentRelation);
+
+	ExecAssignScanType(&scanstate->ss, tupdesc);
 
 	/*
 	 * Initialize result tuple type and projection info.
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d31015c..bf9421e 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"
@@ -170,6 +171,7 @@ ExecInsert(TupleTableSlot *slot,
 	Relation	resultRelationDesc;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
+	int			num_rows = 1;
 
 	/*
 	 * get the heap tuple out of the tuple table slot, making sure we have a
@@ -225,6 +227,14 @@ ExecInsert(TupleTableSlot *slot,
 
 		newId = InvalidOid;
 	}
+	else if (resultRelInfo->ri_fdwroutine)
+	{
+		FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+		num_rows = fdwroutine->ExecForeignInsert(resultRelInfo, tuple);
+
+		newId = InvalidOid;
+	}
 	else
 	{
 		/*
@@ -252,7 +262,7 @@ ExecInsert(TupleTableSlot *slot,
 
 	if (canSetTag)
 	{
-		(estate->es_processed)++;
+		(estate->es_processed) += num_rows;
 		estate->es_lastoid = newId;
 		setLastTid(&(tuple->t_self));
 	}
@@ -285,7 +295,7 @@ ExecInsert(TupleTableSlot *slot,
  * ----------------------------------------------------------------
  */
 static TupleTableSlot *
-ExecDelete(ItemPointer tupleid,
+ExecDelete(Datum rowid,
 		   HeapTupleHeader oldtuple,
 		   TupleTableSlot *planSlot,
 		   EPQState *epqstate,
@@ -296,6 +306,7 @@ ExecDelete(ItemPointer tupleid,
 	Relation	resultRelationDesc;
 	HTSU_Result result;
 	HeapUpdateFailureData hufd;
+	int			num_rows = 1;
 
 	/*
 	 * get information on the (current) result relation
@@ -310,7 +321,7 @@ ExecDelete(ItemPointer tupleid,
 		bool		dodelete;
 
 		dodelete = ExecBRDeleteTriggers(estate, epqstate, resultRelInfo,
-										tupleid);
+										(ItemPointer)DatumGetPointer(rowid));
 
 		if (!dodelete)			/* "do nothing" */
 			return NULL;
@@ -334,8 +345,16 @@ ExecDelete(ItemPointer tupleid,
 		if (!dodelete)			/* "do nothing" */
 			return NULL;
 	}
+	else if (resultRelInfo->ri_fdwroutine)
+	{
+		FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+		num_rows = fdwroutine->ExecForeignDelete(resultRelInfo, rowid);
+	}
 	else
 	{
+		ItemPointer	tupleid = (ItemPointer) DatumGetPointer(rowid);
+
 		/*
 		 * delete the tuple
 		 *
@@ -430,10 +449,11 @@ ldelete:;
 	}
 
 	if (canSetTag)
-		(estate->es_processed)++;
+		(estate->es_processed) += num_rows;
 
 	/* AFTER ROW DELETE Triggers */
-	ExecARDeleteTriggers(estate, resultRelInfo, tupleid);
+	ExecARDeleteTriggers(estate, resultRelInfo,
+						 (ItemPointer)DatumGetPointer(rowid));
 
 	/* Process RETURNING if present */
 	if (resultRelInfo->ri_projectReturning)
@@ -457,7 +477,8 @@ ldelete:;
 		}
 		else
 		{
-			deltuple.t_self = *tupleid;
+			ItemPointerCopy((ItemPointer)DatumGetPointer(rowid),
+							&deltuple.t_self);
 			if (!heap_fetch(resultRelationDesc, SnapshotAny,
 							&deltuple, &delbuffer, false, NULL))
 				elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
@@ -499,7 +520,7 @@ ldelete:;
  * ----------------------------------------------------------------
  */
 static TupleTableSlot *
-ExecUpdate(ItemPointer tupleid,
+ExecUpdate(Datum rowid,
 		   HeapTupleHeader oldtuple,
 		   TupleTableSlot *slot,
 		   TupleTableSlot *planSlot,
@@ -513,6 +534,7 @@ ExecUpdate(ItemPointer tupleid,
 	HTSU_Result result;
 	HeapUpdateFailureData hufd;
 	List	   *recheckIndexes = NIL;
+	int			num_rows = 1;
 
 	/*
 	 * abort the operation if not running transactions
@@ -537,7 +559,7 @@ ExecUpdate(ItemPointer tupleid,
 		resultRelInfo->ri_TrigDesc->trig_update_before_row)
 	{
 		slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
-									tupleid, slot);
+									(ItemPointer)DatumGetPointer(rowid), slot);
 
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
@@ -567,8 +589,16 @@ ExecUpdate(ItemPointer tupleid,
 		/* trigger might have changed tuple */
 		tuple = ExecMaterializeSlot(slot);
 	}
+	else if (resultRelInfo->ri_fdwroutine)
+	{
+		FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+		num_rows = fdwroutine->ExecForeignUpdate(resultRelInfo, rowid, tuple);
+	}
 	else
 	{
+		ItemPointer	tupleid = (ItemPointer) DatumGetPointer(rowid);
+
 		/*
 		 * Check the constraints of the tuple
 		 *
@@ -687,11 +717,12 @@ lreplace:;
 	}
 
 	if (canSetTag)
-		(estate->es_processed)++;
+		(estate->es_processed) += num_rows;
 
 	/* AFTER ROW UPDATE Triggers */
-	ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple,
-						 recheckIndexes);
+	ExecARUpdateTriggers(estate, resultRelInfo,
+						 (ItemPointer) DatumGetPointer(rowid),
+						 tuple, recheckIndexes);
 
 	list_free(recheckIndexes);
 
@@ -771,6 +802,7 @@ ExecModifyTable(ModifyTableState *node)
 	TupleTableSlot *planSlot;
 	ItemPointer tupleid = NULL;
 	ItemPointerData tuple_ctid;
+	Datum		rowid = 0;
 	HeapTupleHeader oldtuple = NULL;
 
 	/*
@@ -859,17 +891,19 @@ ExecModifyTable(ModifyTableState *node)
 		if (junkfilter != NULL)
 		{
 			/*
-			 * extract the 'ctid' or 'wholerow' junk attribute.
+			 * extract the 'ctid', 'rowid' or 'wholerow' junk attribute.
 			 */
 			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,
+												 junkfilter->jf_junkRowidNo,
 												 &isNull);
 					/* shouldn't ever get a null result... */
 					if (isNull)
@@ -877,13 +911,33 @@ ExecModifyTable(ModifyTableState *node)
 
 					tupleid = (ItemPointer) DatumGetPointer(datum);
 					tuple_ctid = *tupleid;		/* be sure we don't free
-												 * ctid!! */
-					tupleid = &tuple_ctid;
+												 * ctid ! */
+					rowid = PointerGetDatum(&tuple_ctid);
+				}
+				else if (relkind == RELKIND_FOREIGN_TABLE)
+				{
+					datum = ExecGetJunkAttribute(slot,
+												 junkfilter->jf_junkRowidNo,
+												 &isNull);
+					/* shouldn't ever get a null result... */
+					if (isNull)
+						elog(ERROR, "rowid is NULL");
+
+					rowid = datum;
+
+					datum = ExecGetJunkAttribute(slot,
+												 junkfilter->jf_junkRecordNo,
+												 &isNull);
+					/* shouldn't ever get a null result... */
+					if (isNull)
+						elog(ERROR, "wholerow is NULL");
+
+					oldtuple = DatumGetHeapTupleHeader(datum);
 				}
 				else
 				{
 					datum = ExecGetJunkAttribute(slot,
-												 junkfilter->jf_junkAttNo,
+												 junkfilter->jf_junkRecordNo,
 												 &isNull);
 					/* shouldn't ever get a null result... */
 					if (isNull)
@@ -906,11 +960,11 @@ ExecModifyTable(ModifyTableState *node)
 				slot = ExecInsert(slot, planSlot, estate, node->canSetTag);
 				break;
 			case CMD_UPDATE:
-				slot = ExecUpdate(tupleid, oldtuple, slot, planSlot,
+				slot = ExecUpdate(rowid, oldtuple, slot, planSlot,
 								&node->mt_epqstate, estate, node->canSetTag);
 				break;
 			case CMD_DELETE:
-				slot = ExecDelete(tupleid, oldtuple, planSlot,
+				slot = ExecDelete(rowid, oldtuple, planSlot,
 								&node->mt_epqstate, estate, node->canSetTag);
 				break;
 			default:
@@ -997,6 +1051,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	i = 0;
 	foreach(l, node->plans)
 	{
+		char	relkind;
+
 		subplan = (Plan *) lfirst(l);
 
 		/*
@@ -1022,6 +1078,24 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		estate->es_result_relation_info = resultRelInfo;
 		mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
 
+		/*
+		 * Also tells FDW extensions to init the plan for this result rel
+		 */
+		relkind = RelationGetForm(resultRelInfo->ri_RelationDesc)->relkind;
+		if (relkind == RELKIND_FOREIGN_TABLE)
+		{
+			Oid		relid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+			FdwRoutine *fdwroutine = GetFdwRoutineByRelId(relid);
+			List   *fdwprivate = list_nth(node->fdwPrivList, i);
+
+			Assert(fdwroutine != NULL);
+			resultRelInfo->ri_fdwroutine = fdwroutine;
+			resultRelInfo->ri_fdw_state = NULL;
+
+			if (fdwroutine->BeginForeignModify)
+				fdwroutine->BeginForeignModify(mtstate, resultRelInfo,
+											   fdwprivate, subplan, eflags);
+		}
 		resultRelInfo++;
 		i++;
 	}
@@ -1163,6 +1237,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			for (i = 0; i < nplans; i++)
 			{
 				JunkFilter *j;
+				char		relkind =
+				    RelationGetForm(resultRelInfo->ri_RelationDesc)->relkind;
 
 				subplan = mtstate->mt_plans[i]->plan;
 				if (operation == CMD_INSERT || operation == CMD_UPDATE)
@@ -1176,16 +1252,27 @@ 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)
+					if (relkind == RELKIND_RELATION)
 					{
-						j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
-						if (!AttributeNumberIsValid(j->jf_junkAttNo))
+						j->jf_junkRowidNo = ExecFindJunkAttribute(j, "ctid");
+						if (!AttributeNumberIsValid(j->jf_junkRowidNo))
 							elog(ERROR, "could not find junk ctid column");
 					}
+					else if (relkind == RELKIND_FOREIGN_TABLE)
+					{
+						j->jf_junkRowidNo = ExecFindJunkAttribute(j, "rowid");
+						if (!AttributeNumberIsValid(j->jf_junkRowidNo))
+							elog(ERROR, "could not find junk rowid column");
+						j->jf_junkRecordNo
+							= ExecFindJunkAttribute(j, "record");
+						if (!AttributeNumberIsValid(j->jf_junkRecordNo))
+							elog(ERROR, "could not find junk wholerow column");
+					}
 					else
 					{
-						j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
-						if (!AttributeNumberIsValid(j->jf_junkAttNo))
+						j->jf_junkRecordNo
+							= ExecFindJunkAttribute(j, "wholerow");
+						if (!AttributeNumberIsValid(j->jf_junkRecordNo))
 							elog(ERROR, "could not find junk wholerow column");
 					}
 				}
@@ -1239,6 +1326,21 @@ ExecEndModifyTable(ModifyTableState *node)
 {
 	int			i;
 
+	/* Let the FDW shut dowm */
+	for (i=0; i < node->ps.state->es_num_result_relations; i++)
+	{
+		ResultRelInfo  *resultRelInfo
+			= &node->ps.state->es_result_relations[i];
+
+		if (resultRelInfo->ri_fdwroutine)
+		{
+			FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+			if (fdwroutine->EndForeignModify)
+				fdwroutine->EndForeignModify(resultRelInfo);
+		}
+	}
+
 	/*
 	 * Free the exprcontext
 	 */
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
index d8845aa..20ddecd 100644
--- a/src/backend/foreign/foreign.c
+++ b/src/backend/foreign/foreign.c
@@ -571,3 +571,37 @@ get_foreign_server_oid(const char *servername, bool missing_ok)
 				 errmsg("server \"%s\" does not exist", servername)));
 	return oid;
 }
+
+/*
+ * get_pseudo_rowid_column
+ *
+ * It picks up an attribute number to be used for pseudo rowid columns,
+ * if exists. It should be injected at rewriteHandler.c if supplied query
+ * is UPDATE or DELETE command. Elsewhere, it returns InvalidAttrNumber.
+ */
+AttrNumber
+get_pseudo_rowid_column(RelOptInfo *baserel, List *targetList)
+{
+	ListCell   *cell;
+
+	foreach (cell, targetList)
+	{
+		TargetEntry *tle = lfirst(cell);
+
+		if (tle->resjunk &&
+			tle->resname && strcmp(tle->resname, "rowid") == 0)
+		{
+			Var	   *var;
+
+			if (!IsA(tle->expr, Var))
+				elog(ERROR, "unexpected node on junk rowid entry: %d",
+					 (int) nodeTag(tle->expr));
+
+			var = (Var *) tle->expr;
+			Assert(baserel->relid == var->varno);
+
+			return var->varattno;
+		}
+	}
+	return InvalidAttrNumber;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 9387ee9..5b1e8ac 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -181,6 +181,7 @@ _copyModifyTable(const ModifyTable *from)
 	COPY_NODE_FIELD(returningLists);
 	COPY_NODE_FIELD(rowMarks);
 	COPY_SCALAR_FIELD(epqParam);
+	COPY_NODE_FIELD(fdwPrivList);
 
 	return newnode;
 }
@@ -594,6 +595,7 @@ _copyForeignScan(const ForeignScan *from)
 	COPY_NODE_FIELD(fdw_exprs);
 	COPY_NODE_FIELD(fdw_private);
 	COPY_SCALAR_FIELD(fsSystemCol);
+	COPY_SCALAR_FIELD(fsPseudoCol);
 
 	return newnode;
 }
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 35c6287..c32ebd3 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -335,6 +335,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
 	WRITE_NODE_FIELD(returningLists);
 	WRITE_NODE_FIELD(rowMarks);
 	WRITE_INT_FIELD(epqParam);
+	WRITE_NODE_FIELD(fdwPrivList);
 }
 
 static void
@@ -562,6 +563,7 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
 	WRITE_NODE_FIELD(fdw_exprs);
 	WRITE_NODE_FIELD(fdw_private);
 	WRITE_BOOL_FIELD(fsSystemCol);
+	WRITE_BOOL_FIELD(fsPseudoCol);
 }
 
 static void
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 030f420..e9edb87 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -39,6 +39,7 @@
 #include "parser/parse_clause.h"
 #include "parser/parsetree.h"
 #include "utils/lsyscache.h"
+#include "utils/rel.h"
 
 
 static Plan *create_plan_recurse(PlannerInfo *root, Path *best_path);
@@ -1943,6 +1944,8 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 	RelOptInfo *rel = best_path->path.parent;
 	Index		scan_relid = rel->relid;
 	RangeTblEntry *rte;
+	Relation	relation;
+	AttrNumber	num_attrs;
 	int			i;
 
 	/* it should be a base rel... */
@@ -2001,6 +2004,22 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 		}
 	}
 
+	/*
+	 * Also, detect whether any pseudo columns are requested from rel.
+	 */
+	relation = heap_open(rte->relid, NoLock);
+	scan_plan->fsPseudoCol = false;
+	num_attrs = RelationGetNumberOfAttributes(relation);
+	for (i = num_attrs + 1; i <= rel->max_attr; i++)
+	{
+		if (!bms_is_empty(rel->attr_needed[i - rel->min_attr]))
+		{
+			scan_plan->fsPseudoCol = true;
+			break;
+		}
+	}
+	heap_close(relation, NoLock);
+
 	return scan_plan;
 }
 
@@ -4695,7 +4714,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)
@@ -4704,6 +4724,8 @@ make_modifytable(CmdType operation, bool canSetTag,
 	Plan	   *plan = &node->plan;
 	double		total_size;
 	ListCell   *subnode;
+	ListCell   *resultRel;
+	List	   *fdw_priv_list = NIL;
 
 	Assert(list_length(resultRelations) == list_length(subplans));
 	Assert(returningLists == NIL ||
@@ -4746,6 +4768,30 @@ make_modifytable(CmdType operation, bool canSetTag,
 	node->rowMarks = rowMarks;
 	node->epqParam = epqParam;
 
+	/*
+	 * Allows FDW driver to construct its private plan if result relation
+	 * is foreign table.
+	 */
+	forboth (resultRel, resultRelations, subnode, subplans)
+	{
+		RangeTblEntry  *rte = rt_fetch(lfirst_int(resultRel),
+									   root->parse->rtable);
+		List		   *fdw_private = NIL;
+		char			relkind = get_rel_relkind(rte->relid);
+
+		if (relkind == RELKIND_FOREIGN_TABLE)
+		{
+			FdwRoutine *fdwroutine = GetFdwRoutineByRelId(rte->relid);
+
+			if (fdwroutine && fdwroutine->PlanForeignModify)
+				fdw_private = fdwroutine->PlanForeignModify(root, node,
+													lfirst_int(resultRel),
+													(Plan *) lfirst(subnode));
+		}
+		fdw_priv_list = lappend(fdw_priv_list, fdw_private);
+	}
+	node->fdwPrivList = fdw_priv_list;
+
 	return node;
 }
 
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index bd719b5..57ba192 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -85,7 +85,7 @@ static void check_hashjoinable(RestrictInfo *restrictinfo);
  * "other rel" RelOptInfos for the members of any appendrels we find here.)
  */
 void
-add_base_rels_to_query(PlannerInfo *root, Node *jtnode)
+add_base_rels_to_query(PlannerInfo *root, List *tlist, Node *jtnode)
 {
 	if (jtnode == NULL)
 		return;
@@ -93,7 +93,7 @@ add_base_rels_to_query(PlannerInfo *root, Node *jtnode)
 	{
 		int			varno = ((RangeTblRef *) jtnode)->rtindex;
 
-		(void) build_simple_rel(root, varno, RELOPT_BASEREL);
+		(void) build_simple_rel(root, varno, tlist, RELOPT_BASEREL);
 	}
 	else if (IsA(jtnode, FromExpr))
 	{
@@ -101,14 +101,14 @@ add_base_rels_to_query(PlannerInfo *root, Node *jtnode)
 		ListCell   *l;
 
 		foreach(l, f->fromlist)
-			add_base_rels_to_query(root, lfirst(l));
+			add_base_rels_to_query(root, tlist, lfirst(l));
 	}
 	else if (IsA(jtnode, JoinExpr))
 	{
 		JoinExpr   *j = (JoinExpr *) jtnode;
 
-		add_base_rels_to_query(root, j->larg);
-		add_base_rels_to_query(root, j->rarg);
+		add_base_rels_to_query(root, tlist, j->larg);
+		add_base_rels_to_query(root, tlist, j->rarg);
 	}
 	else
 		elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
index c2488a4..2d292dd 100644
--- a/src/backend/optimizer/plan/planmain.c
+++ b/src/backend/optimizer/plan/planmain.c
@@ -163,7 +163,7 @@ query_planner(PlannerInfo *root, List *tlist,
 	 * rangetable may contain RTEs for rels not actively part of the query,
 	 * for example views.  We don't want to make RelOptInfos for them.
 	 */
-	add_base_rels_to_query(root, (Node *) parse->jointree);
+	add_base_rels_to_query(root, tlist, (Node *) parse->jointree);
 
 	/*
 	 * Examine the targetlist and join tree, adding entries to baserel
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b61005f..2d72244 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,
@@ -3385,7 +3387,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 	setup_simple_rel_arrays(root);
 
 	/* Build RelOptInfo */
-	rel = build_simple_rel(root, 1, RELOPT_BASEREL);
+	rel = build_simple_rel(root, 1, NIL, RELOPT_BASEREL);
 
 	/* Locate IndexOptInfo for the target index */
 	indexInfo = NULL;
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index b91e9f4..61bc447 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -236,7 +236,8 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 		 * used for anything here, but it carries the subroot data structures
 		 * forward to setrefs.c processing.
 		 */
-		rel = build_simple_rel(root, rtr->rtindex, RELOPT_BASEREL);
+		rel = build_simple_rel(root, rtr->rtindex, refnames_tlist,
+							   RELOPT_BASEREL);
 
 		/* plan_params should not be in use in current query level */
 		Assert(root->plan_params == NIL);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 04d5028..b16cde3 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -25,6 +25,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/heap.h"
+#include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/clauses.h"
@@ -80,7 +81,7 @@ static List *build_index_tlist(PlannerInfo *root, IndexOptInfo *index,
  */
 void
 get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
-				  RelOptInfo *rel)
+				  List *tlist, RelOptInfo *rel)
 {
 	Index		varno = rel->relid;
 	Relation	relation;
@@ -104,6 +105,30 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 	rel->max_attr = RelationGetNumberOfAttributes(relation);
 	rel->reltablespace = RelationGetForm(relation)->reltablespace;
 
+	/*
+	 * Adjust width of attr_needed slot in case when FDW extension wants
+	 * or needs to return pseudo-columns also, not only columns in its
+	 * table definition.
+	 * GetForeignRelWidth, an optional FDW handler, enables FDW extension
+	 * to save properties of pseudo-column on its private field.
+	 * When foreign-table is the target of UPDATE/DELETE, query-rewritter
+	 * injects "rowid" pseudo-column to track remote row to be modified,
+	 * so FDW has to track which varattno shall perform as "rowid".
+	 */
+	if (RelationGetForm(relation)->relkind == RELKIND_FOREIGN_TABLE)
+	{
+		FdwRoutine *fdwroutine = GetFdwRoutineByRelId(relationObjectId);
+
+		if (fdwroutine->GetForeignRelWidth)
+		{
+			rel->max_attr = fdwroutine->GetForeignRelWidth(root, rel,
+														   relation,
+														   inhparent,
+														   tlist);
+			Assert(rel->max_attr >= RelationGetNumberOfAttributes(relation));
+		}
+	}
+
 	Assert(rel->max_attr >= rel->min_attr);
 	rel->attr_needed = (Relids *)
 		palloc0((rel->max_attr - rel->min_attr + 1) * sizeof(Relids));
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index f724714..84b674c 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -80,7 +80,8 @@ setup_simple_rel_arrays(PlannerInfo *root)
  *	  Construct a new RelOptInfo for a base relation or 'other' relation.
  */
 RelOptInfo *
-build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
+build_simple_rel(PlannerInfo *root, int relid, List *tlist,
+				 RelOptKind reloptkind)
 {
 	RelOptInfo *rel;
 	RangeTblEntry *rte;
@@ -133,7 +134,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
 	{
 		case RTE_RELATION:
 			/* Table --- retrieve statistics from the system catalogs */
-			get_relation_info(root, rte->relid, rte->inh, rel);
+			get_relation_info(root, rte->relid, rte->inh, tlist, rel);
 			break;
 		case RTE_SUBQUERY:
 		case RTE_FUNCTION:
@@ -180,7 +181,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
 			if (appinfo->parent_relid != relid)
 				continue;
 
-			(void) build_simple_rel(root, appinfo->child_relid,
+			(void) build_simple_rel(root, appinfo->child_relid, tlist,
 									RELOPT_OTHER_MEMBER_REL);
 		}
 	}
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index b785c26..6cd6241 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1165,7 +1165,10 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 					Relation target_relation)
 {
 	Var		   *var;
-	const char *attrname;
+	List	   *varList;
+	List	   *attNameList;
+	ListCell   *cell1;
+	ListCell   *cell2;
 	TargetEntry *tle;
 
 	if (target_relation->rd_rel->relkind == RELKIND_RELATION)
@@ -1179,8 +1182,35 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 					  -1,
 					  InvalidOid,
 					  0);
+		varList = list_make1(var);
+		attNameList = list_make1("ctid");
+	}
+	else if (target_relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+	{
+		/*
+		 * Emit Rowid so that executor can find the row to update or delete.
+		 */
+		var = makeVar(parsetree->resultRelation,
+					  RelationGetNumberOfAttributes(target_relation) + 1,
+					  CSTRINGOID,
+					  -2,
+					  InvalidOid,
+					  0);
+		varList = list_make1(var);
+
+		/*
+		 * Emit generic record Var so that executor will have the "old" view
+		 * row to pass the RETURNING clause (or upcoming triggers).
+		 */
+		var = makeVar(parsetree->resultRelation,
+					  InvalidAttrNumber,
+					  RECORDOID,
+					  -1,
+					  InvalidOid,
+					  0);
+		varList = lappend(varList, var);
 
-		attrname = "ctid";
+		attNameList = list_make2("rowid", "record");
 	}
 	else
 	{
@@ -1192,16 +1222,21 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 							  parsetree->resultRelation,
 							  0,
 							  false);
-
-		attrname = "wholerow";
+		varList = list_make1(var);
+		attNameList = list_make1("wholerow");
 	}
 
-	tle = makeTargetEntry((Expr *) var,
-						  list_length(parsetree->targetList) + 1,
-						  pstrdup(attrname),
-						  true);
-
-	parsetree->targetList = lappend(parsetree->targetList, tle);
+	/*
+	 * Append them to targetList
+	 */
+	forboth (cell1, varList, cell2, attNameList)
+	{
+		tle = makeTargetEntry((Expr *)lfirst(cell1),
+							  list_length(parsetree->targetList) + 1,
+							  pstrdup((const char *)lfirst(cell2)),
+							  true);
+		parsetree->targetList = lappend(parsetree->targetList, tle);
+	}
 }
 
 
diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
index 721cd25..153932a 100644
--- a/src/include/foreign/fdwapi.h
+++ b/src/include/foreign/fdwapi.h
@@ -14,6 +14,7 @@
 
 #include "nodes/execnodes.h"
 #include "nodes/relation.h"
+#include "utils/rel.h"
 
 /* To avoid including explain.h here, reference ExplainState thus: */
 struct ExplainState;
@@ -22,6 +23,11 @@ struct ExplainState;
 /*
  * Callback function signatures --- see fdwhandler.sgml for more info.
  */
+typedef AttrNumber (*GetForeignRelWidth_function) (PlannerInfo *root,
+												   RelOptInfo *baserel,
+												   Relation foreignrel,
+												   bool inhparent,
+												   List *targetList);
 
 typedef void (*GetForeignRelSize_function) (PlannerInfo *root,
 														RelOptInfo *baserel,
@@ -58,6 +64,24 @@ typedef int (*AcquireSampleRowsFunc) (Relation relation, int elevel,
 typedef bool (*AnalyzeForeignTable_function) (Relation relation,
 												 AcquireSampleRowsFunc *func,
 													BlockNumber *totalpages);
+typedef List *(*PlanForeignModify_function) (PlannerInfo *root,
+											 ModifyTable *plan,
+											 Index resultRelation,
+											 Plan *subplan);
+
+typedef void (*BeginForeignModify_function) (ModifyTableState *mtstate,
+											 ResultRelInfo *resultRelInfo,
+											 List *fdw_private,
+											 Plan *subplan,
+											 int eflags);
+typedef int	(*ExecForeignInsert_function) (ResultRelInfo *resultRelInfo,
+										   HeapTuple tuple);
+typedef int	(*ExecForeignDelete_function) (ResultRelInfo *resultRelInfo,
+										   Datum rowid);
+typedef int	(*ExecForeignUpdate_function) (ResultRelInfo *resultRelInfo,
+										   Datum rowid,
+										   HeapTuple tuple);
+typedef void (*EndForeignModify_function) (ResultRelInfo *resultRelInfo);
 
 /*
  * FdwRoutine is the struct returned by a foreign-data wrapper's handler
@@ -90,6 +114,13 @@ typedef struct FdwRoutine
 	 * not provided.
 	 */
 	AnalyzeForeignTable_function AnalyzeForeignTable;
+	GetForeignRelWidth_function GetForeignRelWidth;
+	PlanForeignModify_function PlanForeignModify;
+	BeginForeignModify_function	BeginForeignModify;
+	ExecForeignInsert_function ExecForeignInsert;
+	ExecForeignDelete_function ExecForeignDelete;
+	ExecForeignUpdate_function ExecForeignUpdate;
+	EndForeignModify_function EndForeignModify;
 } FdwRoutine;
 
 
diff --git a/src/include/foreign/foreign.h b/src/include/foreign/foreign.h
index f8aa99e..823671b 100644
--- a/src/include/foreign/foreign.h
+++ b/src/include/foreign/foreign.h
@@ -14,6 +14,7 @@
 #define FOREIGN_H
 
 #include "nodes/parsenodes.h"
+#include "nodes/relation.h"
 
 
 /* Helper for obtaining username for user mapping */
@@ -81,4 +82,6 @@ extern List *GetForeignColumnOptions(Oid relid, AttrNumber attnum);
 extern Oid	get_foreign_data_wrapper_oid(const char *fdwname, bool missing_ok);
 extern Oid	get_foreign_server_oid(const char *servername, bool missing_ok);
 
+extern AttrNumber get_pseudo_rowid_column(RelOptInfo *baserel,
+										  List *targetList);
 #endif   /* FOREIGN_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index d4911bd..4c079b7 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -268,9 +268,11 @@ typedef struct ProjectionInfo
  *						attribute numbers of the "original" tuple and the
  *						attribute numbers of the "clean" tuple.
  *	  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
+ *	  jf_junkRowidNo:	not used by junkfilter code.  Can be used by caller
+ *						to remember the attno of to track a particular tuple
+ *						being updated or deleted.
  *						(execMain.c stores the "ctid" attno here).
+ *	  jf_junkRecordNo:	Also, the attno of whole-row reference.
  * ----------------
  */
 typedef struct JunkFilter
@@ -280,7 +282,8 @@ typedef struct JunkFilter
 	TupleDesc	jf_cleanTupType;
 	AttrNumber *jf_cleanMap;
 	TupleTableSlot *jf_resultSlot;
-	AttrNumber	jf_junkAttNo;
+	AttrNumber	jf_junkRowidNo;
+	AttrNumber	jf_junkRecordNo;
 } JunkFilter;
 
 /* ----------------
@@ -303,6 +306,8 @@ typedef struct JunkFilter
  *		ConstraintExprs			array of constraint-checking expr states
  *		junkFilter				for removing junk attributes from tuples
  *		projectReturning		for computing a RETURNING list
+ *		fdwroutine				FDW callbacks if foreign table
+ *		fdw_state				opaque state of FDW module, or NULL
  * ----------------
  */
 typedef struct ResultRelInfo
@@ -320,6 +325,8 @@ typedef struct ResultRelInfo
 	List	  **ri_ConstraintExprs;
 	JunkFilter *ri_junkFilter;
 	ProjectionInfo *ri_projectReturning;
+	struct FdwRoutine  *ri_fdwroutine;
+	void	   *ri_fdw_state;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index fb9a863..93481aa 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -175,6 +175,7 @@ typedef struct ModifyTable
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
 	int			epqParam;		/* ID of Param for EvalPlanQual re-eval */
+	List	   *fdwPrivList;	/* private fields for foreign tables */
 } ModifyTable;
 
 /* ----------------
@@ -478,6 +479,7 @@ typedef struct ForeignScan
 	List	   *fdw_exprs;		/* expressions that FDW may evaluate */
 	List	   *fdw_private;	/* private data for FDW */
 	bool		fsSystemCol;	/* true if any "system column" is needed */
+	bool		fsPseudoCol;	/* true if any "pseudo column" is needed */
 } ForeignScan;
 
 
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index de889fb..adfc93d 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -133,7 +133,7 @@ extern Path *reparameterize_path(PlannerInfo *root, Path *path,
  */
 extern void setup_simple_rel_arrays(PlannerInfo *root);
 extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid,
-				 RelOptKind reloptkind);
+			   List *tlist, RelOptKind reloptkind);
 extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid);
 extern RelOptInfo *find_join_rel(PlannerInfo *root, Relids relids);
 extern RelOptInfo *build_join_rel(PlannerInfo *root,
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index e0d04db..c5fce41 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -26,7 +26,7 @@ extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook;
 
 
 extern void get_relation_info(PlannerInfo *root, Oid relationObjectId,
-				  bool inhparent, RelOptInfo *rel);
+				  bool inhparent, List *tlist, RelOptInfo *rel);
 
 extern void estimate_rel_size(Relation rel, int32 *attr_widths,
 				  BlockNumber *pages, double *tuples, double *allvisfrac);
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 0fe696c..746a603 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);
@@ -90,7 +91,8 @@ extern bool is_projection_capable_plan(Plan *plan);
 extern int	from_collapse_limit;
 extern int	join_collapse_limit;
 
-extern void add_base_rels_to_query(PlannerInfo *root, Node *jtnode);
+extern void add_base_rels_to_query(PlannerInfo *root, List *tlist,
+								   Node *jtnode);
 extern void build_base_rel_tlists(PlannerInfo *root, List *final_tlist);
 extern void add_vars_to_targetlist(PlannerInfo *root, List *vars,
 					   Relids where_needed, bool create_new_ph);
#39Albe Laurenz
laurenz.albe@wien.gv.at
In reply to: Kohei KaiGai (#38)
Re: [v9.3] writable foreign tables

Kohei KaiGai wrote:

The attached patch is revised version.

One most difference from the previous version is, it constructed
PoC features on Hanada-san's latest postgres-fdw.v5.patch.
Yesh, it looks to me working fine on RDBMS backend also.

Cool.

Even though the filename of this patch contains "poc" phrase,
I think it may be time to consider adoption of the core regarding
to the interface portion.
(Of course, postgres_fdw is still works in progress.)

Ok, I'll try to review it with regard to that.

Here is a few operation examples.

[...]

postgres=# INSERT INTO tbl VALUES (7,'ggg'),(8,'hhh');
INSERT 0 2

Weird, that fails for me.

CREATE TABLE test(
id integer PRIMARY KEY,
val text NOT NULL
);

CREATE FOREIGN TABLE rtest(
id integer not null,
val text not null
) SERVER loopback OPTIONS (nspname 'laurenz', relname 'test');

test=> INSERT INTO test(id, val) VALUES (1, 'one');
INSERT 0 1
test=> INSERT INTO rtest(id, val) VALUES (2, 'two');
The connection to the server was lost. Attempting reset: Failed.
!>

Here is the stack trace:
317 RangeTblEntry *rte = root->simple_rte_array[rtindex];
#0 0x00231cb9 in deparseInsertSql (buf=0xbfffafb0, root=0x9be3614, rtindex=1) at deparse.c:317
#1 0x0023018c in postgresPlanForeignModify (root=0x9be3614, plan=0x9be3278, resultRelaion=1, subplan=0x9be3bec)
at postgres_fdw.c:1538
#2 0x082a3ac2 in make_modifytable (root=0x9be3614, operation=CMD_INSERT, canSetTag=1 '\001', resultRelations=0x9be3c7c,
subplans=0x9be3c4c, returningLists=0x0, rowMarks=0x0, epqParam=0) at createplan.c:4787
#3 0x082a7ada in subquery_planner (glob=0x9be357c, parse=0x9be3304, parent_root=0x0, hasRecursion=0 '\0', tuple_fraction=0,
subroot=0xbfffb0ec) at planner.c:574
#4 0x082a71dc in standard_planner (parse=0x9be3304, cursorOptions=0, boundParams=0x0) at planner.c:200
#5 0x082a707b in planner (parse=0x9be3304, cursorOptions=0, boundParams=0x0) at planner.c:129
#6 0x0832c14c in pg_plan_query (querytree=0x9be3304, cursorOptions=0, boundParams=0x0) at postgres.c:753
#7 0x0832c1ec in pg_plan_queries (querytrees=0x9be3a98, cursorOptions=0, boundParams=0x0) at postgres.c:812
#8 0x0832c46e in exec_simple_query (query_string=0x9be267c "INSERT INTO rtest(id, val) VALUES (2, 'two');") at postgres.c:977

(gdb) print root->simple_rte_array
$1 = (RangeTblEntry **) 0x0

The bug I reported in my last review does not seem to be fixed, either.
The symptoms are different now (with the definitions from above):

test=> UPDATE rtest SET val='new' FROM rtest t2 WHERE rtest.id=t2.id AND t2.val LIKE '%e';
TRAP: FailedAssertion("!(baserel->relid == var->varno)", File: "foreign.c", Line: 601)
The connection to the server was lost. Attempting reset: Failed.
!>

The same happens for DELETE ... USING.

A different failure happens if I join with a local table:

test=> UPDATE rtest SET val='new' FROM test t2 WHERE rtest.id=t2.id AND t2.val = 'nonexist';
TRAP: FailedAssertion("!(((((const Node*)(fscan))->type) == T_ForeignScan))", File: "postgres_fdw.c", Line: 1526)
The connection to the server was lost. Attempting reset: Failed.
!>

I gave up testing the functionality after that.

So, let's back to the main topic of this patch.
According to the upthread discussion, I renamed the interface to inform
expected width of result set as follows:

+typedef AttrNumber (*GetForeignRelWidth_function) (PlannerInfo *root,
+                                                  RelOptInfo *baserel,
+                                                  Relation foreignrel,
+                                                  bool inhparent,
+                                                  List *targetList);

It informs the core how many slots for regular and pseudo columns shall
be acquired. If it is identical with number of attributed in foreign table
definition, it also means this scan does not use any pseudo columns.
A typical use case of pseudo column is "rowid" to move an identifier of
remote row from scan stage to modify stage. It is responsibility of FDW
driver to ensure "rowid" has uniqueness on the remote side; my
enhancement on postgres_fdw uses ctid.

get_pseudo_rowid_column() is a utility function that picks up an attribute
number of pseudo "rowid" column if query rewriter injected on previous
stage. If FDW does not support any other pseudo column features, the
value to be returned is just return-value of this function.

Thanks, I think this will make the FDW writer's work easier.

Other relevant APIs are as follows:

+typedef List *(*PlanForeignModify_function) (PlannerInfo *root,
+                                            ModifyTable *plan,
+                                            Index resultRelation,
+                                            Plan *subplan);
+
I newly added this handler on construction of ModifyTable structure.
Because INSERT command does not have scan stage directly connected
with table modification, FDW driver has no chance to construct its private
stuff relevant to table modification. (In case postgres_fdw, it constructs
the second query to modify remote table with/without given ctid.)
Its returned List * value is moved to BeginForeignModify handler as
third argument.

So, in the case of an INSERT, GetForeignPlan and friends are not
called? Then such a functionality is indeed desirable.

+typedef void (*BeginForeignModify_function) (ModifyTableState *mtstate,
+                                            ResultRelInfo *resultRelInfo,
+                                            List *fdw_private,
+                                            Plan *subplan,
+                                            int eflags);
I adjusted some arguments to reference fdw_private being constructed
on query plan stage. The role of this handler is not changed. FDW driver
should have all the initialization stuff on this handler, like we are doing at
BeginForeignScan.

I wonder about the PlanForeignModify/BeginForeignModify pair.
It is quite different from the "Scan" functions.

Would it make sense to invent a ForeignModifyTableState that has
the fdw_private information included, similar to ForeignScanState?
It might make the API more homogenous.
But maybe that's overkill.

I took a brief look at the documentation; that will need some more
work.

Yours,
Laurenz Albe

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#40Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Albe Laurenz (#39)
1 attachment(s)
Re: [v9.3] writable foreign tables

2012/12/7 Albe Laurenz <laurenz.albe@wien.gv.at>:

Kohei KaiGai wrote:

The attached patch is revised version.

One most difference from the previous version is, it constructed
PoC features on Hanada-san's latest postgres-fdw.v5.patch.
Yesh, it looks to me working fine on RDBMS backend also.

Cool.

Even though the filename of this patch contains "poc" phrase,
I think it may be time to consider adoption of the core regarding
to the interface portion.
(Of course, postgres_fdw is still works in progress.)

Ok, I'll try to review it with regard to that.

Here is a few operation examples.

[...]

postgres=# INSERT INTO tbl VALUES (7,'ggg'),(8,'hhh');
INSERT 0 2

Weird, that fails for me.

CREATE TABLE test(
id integer PRIMARY KEY,
val text NOT NULL
);

CREATE FOREIGN TABLE rtest(
id integer not null,
val text not null
) SERVER loopback OPTIONS (nspname 'laurenz', relname 'test');

test=> INSERT INTO test(id, val) VALUES (1, 'one');
INSERT 0 1
test=> INSERT INTO rtest(id, val) VALUES (2, 'two');
The connection to the server was lost. Attempting reset: Failed.
!>

Here is the stack trace:
317 RangeTblEntry *rte = root->simple_rte_array[rtindex];
#0 0x00231cb9 in deparseInsertSql (buf=0xbfffafb0, root=0x9be3614, rtindex=1) at deparse.c:317
#1 0x0023018c in postgresPlanForeignModify (root=0x9be3614, plan=0x9be3278, resultRelaion=1, subplan=0x9be3bec)
at postgres_fdw.c:1538
#2 0x082a3ac2 in make_modifytable (root=0x9be3614, operation=CMD_INSERT, canSetTag=1 '\001', resultRelations=0x9be3c7c,
subplans=0x9be3c4c, returningLists=0x0, rowMarks=0x0, epqParam=0) at createplan.c:4787
#3 0x082a7ada in subquery_planner (glob=0x9be357c, parse=0x9be3304, parent_root=0x0, hasRecursion=0 '\0', tuple_fraction=0,
subroot=0xbfffb0ec) at planner.c:574
#4 0x082a71dc in standard_planner (parse=0x9be3304, cursorOptions=0, boundParams=0x0) at planner.c:200
#5 0x082a707b in planner (parse=0x9be3304, cursorOptions=0, boundParams=0x0) at planner.c:129
#6 0x0832c14c in pg_plan_query (querytree=0x9be3304, cursorOptions=0, boundParams=0x0) at postgres.c:753
#7 0x0832c1ec in pg_plan_queries (querytrees=0x9be3a98, cursorOptions=0, boundParams=0x0) at postgres.c:812
#8 0x0832c46e in exec_simple_query (query_string=0x9be267c "INSERT INTO rtest(id, val) VALUES (2, 'two');") at postgres.c:977

(gdb) print root->simple_rte_array
$1 = (RangeTblEntry **) 0x0

The reason why simple_rte_array was not initialized is, invocation of
setup_simple_rel_arrays() was skipped in case of simple INSERT INTO
... VALUES(). So, I put setup_simple_rel_arrays() just after short-cut
code path at query_planner().

The bug I reported in my last review does not seem to be fixed, either.
The symptoms are different now (with the definitions from above):

test=> UPDATE rtest SET val='new' FROM rtest t2 WHERE rtest.id=t2.id AND t2.val LIKE '%e';
TRAP: FailedAssertion("!(baserel->relid == var->varno)", File: "foreign.c", Line: 601)
The connection to the server was lost. Attempting reset: Failed.
!>

The same happens for DELETE ... USING.

A different failure happens if I join with a local table:

test=> UPDATE rtest SET val='new' FROM test t2 WHERE rtest.id=t2.id AND t2.val = 'nonexist';
TRAP: FailedAssertion("!(((((const Node*)(fscan))->type) == T_ForeignScan))", File: "postgres_fdw.c", Line: 1526)
The connection to the server was lost. Attempting reset: Failed.
!>

I gave up testing the functionality after that.

The reason of this assertion trap is, I incorrectly assumed subplan of
ModifyTable is always ForeignScan. However, it should be picked up
it from the subplan tree using recursive node walking.
I newly added lookup_foreign_scan_plan() not to reinvent same logic
for each extension. It returns ForeignScan node that has supplied
rtindex, even if the top of subplan tree is not ForeignScan.

Both of the troubles you reported was fixed with attached patch.
I also added relevant test cases into regression test, please check it.

So, let's back to the main topic of this patch.
According to the upthread discussion, I renamed the interface to inform
expected width of result set as follows:

+typedef AttrNumber (*GetForeignRelWidth_function) (PlannerInfo *root,
+                                                  RelOptInfo *baserel,
+                                                  Relation foreignrel,
+                                                  bool inhparent,
+                                                  List *targetList);

It informs the core how many slots for regular and pseudo columns shall
be acquired. If it is identical with number of attributed in foreign table
definition, it also means this scan does not use any pseudo columns.
A typical use case of pseudo column is "rowid" to move an identifier of
remote row from scan stage to modify stage. It is responsibility of FDW
driver to ensure "rowid" has uniqueness on the remote side; my
enhancement on postgres_fdw uses ctid.

get_pseudo_rowid_column() is a utility function that picks up an attribute
number of pseudo "rowid" column if query rewriter injected on previous
stage. If FDW does not support any other pseudo column features, the
value to be returned is just return-value of this function.

Thanks, I think this will make the FDW writer's work easier.

I adjusted the code a bit to work this routine correctly, even if a query
contains two or more foreign table references.

Other relevant APIs are as follows:

+typedef List *(*PlanForeignModify_function) (PlannerInfo *root,
+                                            ModifyTable *plan,
+                                            Index resultRelation,
+                                            Plan *subplan);
+
I newly added this handler on construction of ModifyTable structure.
Because INSERT command does not have scan stage directly connected
with table modification, FDW driver has no chance to construct its private
stuff relevant to table modification. (In case postgres_fdw, it constructs
the second query to modify remote table with/without given ctid.)
Its returned List * value is moved to BeginForeignModify handler as
third argument.

So, in the case of an INSERT, GetForeignPlan and friends are not
called? Then such a functionality is indeed desirable.

Yes. In case of INSERT, its resultRelation is not appeared in the fromList,
thus, no chance to create relevant scan plan towards the table to be inserted.

+typedef void (*BeginForeignModify_function) (ModifyTableState *mtstate,
+                                            ResultRelInfo *resultRelInfo,
+                                            List *fdw_private,
+                                            Plan *subplan,
+                                            int eflags);
I adjusted some arguments to reference fdw_private being constructed
on query plan stage. The role of this handler is not changed. FDW driver
should have all the initialization stuff on this handler, like we are doing at
BeginForeignScan.

I wonder about the PlanForeignModify/BeginForeignModify pair.
It is quite different from the "Scan" functions.

Would it make sense to invent a ForeignModifyTableState that has
the fdw_private information included, similar to ForeignScanState?
It might make the API more homogenous.
But maybe that's overkill.

The origin of this asymmetry is ModifyTableState may affect multiple
tables if result relation has inheritance, even though foreign table does
not support inheritance feature right now.
Please see the entrypoint to invoke BeginForeignModify handler.
It is in a loop block that initializes executor state being associated with
thie ModifyTableState.

Even if we have symmetric definition, the variable "i" need to be delivered
to identify which executor state is under initialization at least.
The relevant resultRelInfo, fdwprivate, and subplan can be picked up
from the ModifyTableState using this index. But it enforces extensions
to implement same logic with individual code. My preference is to pick
up necessary information at the code side.

I took a brief look at the documentation; that will need some more
work.

Yep, I believe it should be revised prior to this patch being committed.

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

Attachments:

pgsql-v9.3-writable-fdw-poc.v6.patchapplication/octet-stream; name=pgsql-v9.3-writable-fdw-poc.v6.patchDownload
 contrib/postgres_fdw/connection.c              |   70 +-
 contrib/postgres_fdw/connection.h              |    8 +-
 contrib/postgres_fdw/deparse.c                 |  126 ++-
 contrib/postgres_fdw/expected/postgres_fdw.out | 1055 ++++++++++++++++++++++++
 contrib/postgres_fdw/postgres_fdw.c            |  408 ++++++++-
 contrib/postgres_fdw/postgres_fdw.h            |    6 +-
 contrib/postgres_fdw/sql/postgres_fdw.sql      |   13 +
 doc/src/sgml/fdwhandler.sgml                   |  152 +++-
 src/backend/executor/execMain.c                |   34 +-
 src/backend/executor/nodeForeignscan.c         |  136 ++-
 src/backend/executor/nodeModifyTable.c         |  150 +++-
 src/backend/foreign/foreign.c                  |   68 ++
 src/backend/nodes/copyfuncs.c                  |    2 +
 src/backend/nodes/outfuncs.c                   |    2 +
 src/backend/optimizer/plan/createplan.c        |   48 +-
 src/backend/optimizer/plan/initsplan.c         |   10 +-
 src/backend/optimizer/plan/planmain.c          |    5 +-
 src/backend/optimizer/plan/planner.c           |    8 +-
 src/backend/optimizer/prep/prepunion.c         |    3 +-
 src/backend/optimizer/util/plancat.c           |   27 +-
 src/backend/optimizer/util/relnode.c           |    7 +-
 src/backend/rewrite/rewriteHandler.c           |   55 +-
 src/include/foreign/fdwapi.h                   |   31 +
 src/include/foreign/foreign.h                  |    6 +
 src/include/nodes/execnodes.h                  |   13 +-
 src/include/nodes/plannodes.h                  |    2 +
 src/include/optimizer/pathnode.h               |    2 +-
 src/include/optimizer/plancat.h                |    2 +-
 src/include/optimizer/planmain.h               |    6 +-
 29 files changed, 2361 insertions(+), 94 deletions(-)

diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index eab8b87..9ca7fbf 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -43,7 +43,7 @@ typedef struct ConnCacheEntry
 	Oid				serverid;	/* oid of foreign server */
 	Oid				userid;		/* oid of local user */
 
-	bool			use_tx;		/* true when using remote transaction */
+	int				conntx;		/* one of PGSQL_FDW_CONNTX_* */
 	int				refs;		/* reference counter */
 	PGconn		   *conn;		/* foreign server connection */
 } ConnCacheEntry;
@@ -65,6 +65,8 @@ cleanup_connection(ResourceReleasePhase phase,
 static PGconn *connect_pg_server(ForeignServer *server, UserMapping *user);
 static void begin_remote_tx(PGconn *conn);
 static void abort_remote_tx(PGconn *conn);
+static void commit_remote_tx(PGconn *conn);
+static void deallocate_remote_prepare(PGconn *conn);
 
 /*
  * Get a PGconn which can be used to execute foreign query on the remote
@@ -80,7 +82,7 @@ static void abort_remote_tx(PGconn *conn);
  * FDW object to invalidate already established connections.
  */
 PGconn *
-GetConnection(ForeignServer *server, UserMapping *user, bool use_tx)
+GetConnection(ForeignServer *server, UserMapping *user, int conntx)
 {
 	bool			found;
 	ConnCacheEntry *entry;
@@ -126,7 +128,7 @@ GetConnection(ForeignServer *server, UserMapping *user, bool use_tx)
 	entry = hash_search(ConnectionHash, &key, HASH_ENTER, &found);
 	if (!found)
 	{
-		entry->use_tx = false;
+		entry->conntx = PGSQL_FDW_CONNTX_NONE;
 		entry->refs = 0;
 		entry->conn = NULL;
 	}
@@ -162,7 +164,7 @@ GetConnection(ForeignServer *server, UserMapping *user, bool use_tx)
 		{
 			/* Clear connection cache entry on error case. */
 			PQfinish(entry->conn);
-			entry->use_tx = false;
+			entry->conntx = PGSQL_FDW_CONNTX_NONE;
 			entry->refs = 0;
 			entry->conn = NULL;
 			PG_RE_THROW();
@@ -182,10 +184,11 @@ GetConnection(ForeignServer *server, UserMapping *user, bool use_tx)
 	 * are in.  We need to remember whether this connection uses remote
 	 * transaction to abort it when this connection is released completely.
 	 */
-	if (use_tx && !entry->use_tx)
+	if (conntx > entry->conntx)
 	{
-		begin_remote_tx(entry->conn);
-		entry->use_tx = use_tx;
+		if (entry->conntx == PGSQL_FDW_CONNTX_NONE)
+			begin_remote_tx(entry->conn);
+		entry->conntx = conntx;
 	}
 
 	return entry->conn;
@@ -355,12 +358,45 @@ abort_remote_tx(PGconn *conn)
 	PQclear(res);
 }
 
+static void
+commit_remote_tx(PGconn *conn)
+{
+	PGresult	   *res;
+
+	elog(DEBUG3, "committing remote transaction");
+
+	res = PQexec(conn, "COMMIT TRANSACTION");
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		PQclear(res);
+		elog(ERROR, "could not commit transaction: %s", PQerrorMessage(conn));
+	}
+	PQclear(res);
+}
+
+static void
+deallocate_remote_prepare(PGconn *conn)
+{
+	PGresult	   *res;
+
+	elog(DEBUG3, "deallocating remote prepares");
+
+	res = PQexec(conn, "DEALLOCATE PREPARE ALL");
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		PQclear(res);
+		elog(ERROR, "could not deallocate prepared statement: %s",
+			 PQerrorMessage(conn));
+	}
+	PQclear(res);
+}
+
 /*
  * Mark the connection as "unused", and close it if the caller was the last
  * user of the connection.
  */
 void
-ReleaseConnection(PGconn *conn)
+ReleaseConnection(PGconn *conn, bool is_abort)
 {
 	HASH_SEQ_STATUS		scan;
 	ConnCacheEntry	   *entry;
@@ -412,7 +448,7 @@ ReleaseConnection(PGconn *conn)
 			 PQtransactionStatus(conn) == PQTRANS_INERROR ? "INERROR" :
 			 "UNKNOWN");
 		PQfinish(conn);
-		entry->use_tx = false;
+		entry->conntx = PGSQL_FDW_CONNTX_NONE;
 		entry->refs = 0;
 		entry->conn = NULL;
 		return;
@@ -430,10 +466,16 @@ ReleaseConnection(PGconn *conn)
 	 * If this connection uses remote transaction and there is no user other
 	 * than the caller, abort the remote transaction and forget about it.
 	 */
-	if (entry->use_tx && entry->refs == 0)
+	if (entry->conntx > PGSQL_FDW_CONNTX_NONE && entry->refs == 0)
 	{
-		abort_remote_tx(conn);
-		entry->use_tx = false;
+		if (entry->conntx > PGSQL_FDW_CONNTX_READ_ONLY)
+			deallocate_remote_prepare(conn);
+		if (is_abort || entry->conntx == PGSQL_FDW_CONNTX_READ_ONLY)
+			abort_remote_tx(conn);
+		else
+			commit_remote_tx(conn);
+
+		entry->conntx = PGSQL_FDW_CONNTX_NONE;
 	}
 }
 
@@ -485,7 +527,7 @@ cleanup_connection(ResourceReleasePhase phase,
 		elog(DEBUG3, "discard postgres_fdw connection %p due to resowner cleanup",
 			 entry->conn);
 		PQfinish(entry->conn);
-		entry->use_tx = false;
+		entry->conntx = PGSQL_FDW_CONNTX_NONE;
 		entry->refs = 0;
 		entry->conn = NULL;
 	}
@@ -597,7 +639,7 @@ postgres_fdw_disconnect(PG_FUNCTION_ARGS)
 
 	/* Discard cached connection, and clear reference counter. */
 	PQfinish(entry->conn);
-	entry->use_tx = false;
+	entry->conntx = PGSQL_FDW_CONNTX_NONE;
 	entry->refs = 0;
 	entry->conn = NULL;
 
diff --git a/contrib/postgres_fdw/connection.h b/contrib/postgres_fdw/connection.h
index 4c9d850..f97cc8a 100644
--- a/contrib/postgres_fdw/connection.h
+++ b/contrib/postgres_fdw/connection.h
@@ -16,10 +16,14 @@
 #include "foreign/foreign.h"
 #include "libpq-fe.h"
 
+#define PGSQL_FDW_CONNTX_NONE			0
+#define PGSQL_FDW_CONNTX_READ_ONLY		1
+#define PGSQL_FDW_CONNTX_READ_WRITE		2
+
 /*
  * Connection management
  */
-PGconn *GetConnection(ForeignServer *server, UserMapping *user, bool use_tx);
-void ReleaseConnection(PGconn *conn);
+PGconn *GetConnection(ForeignServer *server, UserMapping *user, int conntx);
+void ReleaseConnection(PGconn *conn, bool is_abort);
 
 #endif /* CONNECTION_H */
diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
index 69e6a3e..9e09429 100644
--- a/contrib/postgres_fdw/deparse.c
+++ b/contrib/postgres_fdw/deparse.c
@@ -12,6 +12,7 @@
  */
 #include "postgres.h"
 
+#include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/transam.h"
 #include "catalog/pg_class.h"
@@ -86,9 +87,11 @@ void
 deparseSimpleSql(StringInfo buf,
 				 PlannerInfo *root,
 				 RelOptInfo *baserel,
-				 List *local_conds)
+				 List *local_conds,
+				 AttrNumber anum_rowid)
 {
 	RangeTblEntry *rte;
+	Relation	rel;
 	ListCell   *lc;
 	StringInfoData	foreign_relname;
 	bool		first;
@@ -125,6 +128,24 @@ deparseSimpleSql(StringInfo buf,
 	}
 
 	/*
+	 * XXX - When this foreign table is target relation and RETURNING
+	 * clause reference some column, we have to mark these columns as
+	 * in-use. It is needed to support DELETE command, because INSERT
+	 * and UPDATE implicitly add references to all the regular columns
+	 * on baserel->reltargetlist.
+	 */
+	if (root->parse->resultRelation == baserel->relid &&
+		root->parse->returningList)
+	{
+		List   *attrs;
+
+		attrs = pull_var_clause((Node *) root->parse->returningList,
+								PVC_RECURSE_AGGREGATES,
+                                PVC_RECURSE_PLACEHOLDERS);
+		attr_used = list_union(attr_used, attrs);
+	}
+
+	/*
 	 * deparse SELECT clause
 	 *
 	 * List attributes which are in either target list or local restriction.
@@ -136,9 +157,10 @@ deparseSimpleSql(StringInfo buf,
 	 */
 	appendStringInfo(buf, "SELECT ");
 	rte = root->simple_rte_array[baserel->relid];
+	rel = heap_open(rte->relid, NoLock);
 	attr_used = list_union(attr_used, baserel->reltargetlist);
 	first = true;
-	for (attr = 1; attr <= baserel->max_attr; attr++)
+	for (attr = 1; attr <= RelationGetNumberOfAttributes(rel); attr++)
 	{
 		Var		   *var = NULL;
 		ListCell   *lc;
@@ -167,6 +189,10 @@ deparseSimpleSql(StringInfo buf,
 		else
 			appendStringInfo(buf, "NULL");
 	}
+	if (anum_rowid != InvalidAttrNumber)
+		appendStringInfo(buf, "%sctid", (first ? "" : ","));
+
+	heap_close(rel, NoLock);
 	appendStringInfoChar(buf, ' ');
 
 	/*
@@ -283,6 +309,102 @@ deparseAnalyzeSql(StringInfo buf, Relation rel)
 }
 
 /*
+ * deparse remote INSERT statement
+ */
+void
+deparseInsertSql(StringInfo buf, PlannerInfo *root, Index rtindex)
+{
+	RangeTblEntry  *rte = root->simple_rte_array[rtindex];
+	Relation		frel = heap_open(rte->relid, NoLock);
+	int				i, j, nattrs = RelationGetNumberOfAttributes(frel);
+	bool			is_first = true;
+
+	appendStringInfo(buf, "INSERT INTO ");
+	deparseRelation(buf, rte);
+	appendStringInfo(buf, "(");
+
+	for (i=0; i < nattrs; i++)
+	{
+		Form_pg_attribute attr = RelationGetDescr(frel)->attrs[i];
+		Var		var;
+
+		if (attr->attisdropped)
+			continue;
+
+		if (!is_first)
+			appendStringInfo(buf, ",");
+
+		var.varno = rtindex;
+		var.varattno = attr->attnum;
+		deparseVar(buf, &var, root);
+		is_first = false;
+	}
+	appendStringInfo(buf, ") VALUES (");
+
+	for (i=0, j=1; i < nattrs; i++)
+	{
+		Form_pg_attribute attr = RelationGetDescr(frel)->attrs[i];
+
+		if (attr->attisdropped)
+			continue;
+
+		appendStringInfo(buf, "%s$%d", (j == 1 ? "" : ","), j);
+		j++;
+	}
+	appendStringInfo(buf, ")");
+	heap_close(frel, NoLock);
+}
+
+/*
+ * deparse remote UPDATE statement
+ */
+void
+deparseUpdateSql(StringInfo buf, PlannerInfo *root, Index rtindex)
+{
+	RangeTblEntry  *rte = root->simple_rte_array[rtindex];
+	Relation		frel = heap_open(rte->relid, NoLock);
+	int				i, j, nattrs = RelationGetNumberOfAttributes(frel);
+	bool			is_first = true;
+
+	appendStringInfo(buf, "UPDATE ");
+	deparseRelation(buf, rte);
+	appendStringInfo(buf, " SET ");
+
+	for (i=0, j=2; i < nattrs; i++)
+	{
+		Form_pg_attribute attr = RelationGetDescr(frel)->attrs[i];
+		Var		var;
+
+		if (attr->attisdropped)
+			continue;
+
+		if (!is_first)
+			appendStringInfo(buf, ",");
+
+		var.varno = rtindex;
+		var.varattno = attr->attnum;
+		deparseVar(buf, &var, root);
+		appendStringInfo(buf, "=$%d", j++);
+		is_first = false;
+	}
+	appendStringInfo(buf, " WHERE ctid=$1");
+	heap_close(frel, NoLock);
+}
+
+/*
+ * deparse remote DELETE statement
+ */
+void
+deparseDeleteSql(StringInfo buf, PlannerInfo *root, Index rtindex)
+{
+	RangeTblEntry  *rte = root->simple_rte_array[rtindex];
+
+	appendStringInfo(buf, "DELETE FROM ");
+	deparseRelation(buf, rte);
+	appendStringInfo(buf, " WHERE ctid = $1");
+}
+
+/*
  * Deparse given expression into buf.  Actual string operation is delegated to
  * node-type-specific functions.
  *
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index f81c727..db2c725 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -736,6 +736,1061 @@ SELECT srvname FROM postgres_fdw_connections;
 (0 rows)
 
 -- ===================================================================
+-- test for writable foreign table stuff (PoC stage now)
+-- ===================================================================
+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)
+
+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)
+
+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)
+
+-- ===================================================================
 -- cleanup
 -- ===================================================================
 DROP OPERATOR === (int, int) CASCADE;
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 6b870ab..637f31c 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -59,6 +59,7 @@ typedef struct PostgresFdwPlanState {
 	List		   *param_conds;
 	List		   *local_conds;
 	int				width;			/* obtained by remote EXPLAIN */
+	AttrNumber		anum_rowid;
 
 	/* Cached catalog information. */
 	ForeignTable   *table;
@@ -150,6 +151,20 @@ typedef struct PostgresAnalyzeState
 } PostgresAnalyzeState;
 
 /*
+ * Describes a state of modify request for a foreign table
+ */
+typedef struct PostgresFdwModifyState
+{
+	PGconn	   *conn;
+	char	   *query;
+	char	   *p_name;
+	int			p_nums;
+	Oid		   *p_types;
+	FmgrInfo   *p_flinfo;
+	MemoryContext	es_query_cxt;
+} PostgresFdwModifyState;
+
+/*
  * SQL functions
  */
 extern Datum postgres_fdw_handler(PG_FUNCTION_ARGS);
@@ -158,6 +173,11 @@ PG_FUNCTION_INFO_V1(postgres_fdw_handler);
 /*
  * FDW callback routines
  */
+static AttrNumber postgresGetForeignRelWidth(PlannerInfo *root,
+											 RelOptInfo *baserel,
+											 Relation foreignrel,
+											 bool inhparent,
+											 List *targetList);
 static void postgresGetForeignRelSize(PlannerInfo *root,
 									  RelOptInfo *baserel,
 									  Oid foreigntableid);
@@ -179,6 +199,23 @@ static void postgresEndForeignScan(ForeignScanState *node);
 static bool postgresAnalyzeForeignTable(Relation relation,
 										AcquireSampleRowsFunc *func,
 										BlockNumber *totalpages);
+static List *postgresPlanForeignModify(PlannerInfo *root,
+									   ModifyTable *plan,
+									   Index resultRelation,
+									   Plan *subplan);
+static void postgresBeginForeignModify(ModifyTableState *mtstate,
+									   ResultRelInfo *resultRelInfo,
+									   List *fdw_private,
+									   Plan *subplan,
+									   int eflags);
+static int postgresExecForeignInsert(ResultRelInfo *resultRelInfo,
+									 HeapTuple tuple);
+static int postgresExecForeignDelete(ResultRelInfo *resultRelInfo,
+									 Datum rowid);
+static int postgresExecForeignUpdate(ResultRelInfo *resultRelInfo,
+									 Datum rowid,
+									 HeapTuple tuple);
+static void postgresEndForeignModify(ResultRelInfo *resultRelInfo);
 
 /*
  * Helper functions
@@ -231,6 +268,7 @@ postgres_fdw_handler(PG_FUNCTION_ARGS)
 	FdwRoutine	*routine = makeNode(FdwRoutine);
 
 	/* Required handler functions. */
+	routine->GetForeignRelWidth = postgresGetForeignRelWidth;
 	routine->GetForeignRelSize = postgresGetForeignRelSize;
 	routine->GetForeignPaths = postgresGetForeignPaths;
 	routine->GetForeignPlan = postgresGetForeignPlan;
@@ -239,6 +277,12 @@ postgres_fdw_handler(PG_FUNCTION_ARGS)
 	routine->IterateForeignScan = postgresIterateForeignScan;
 	routine->ReScanForeignScan = postgresReScanForeignScan;
 	routine->EndForeignScan = postgresEndForeignScan;
+	routine->PlanForeignModify = postgresPlanForeignModify;
+	routine->BeginForeignModify = postgresBeginForeignModify;
+	routine->ExecForeignInsert = postgresExecForeignInsert;
+	routine->ExecForeignDelete = postgresExecForeignDelete;
+	routine->ExecForeignUpdate = postgresExecForeignUpdate;
+	routine->EndForeignModify = postgresEndForeignModify;
 
 	/* Optional handler functions. */
 	routine->AnalyzeForeignTable = postgresAnalyzeForeignTable;
@@ -247,6 +291,29 @@ postgres_fdw_handler(PG_FUNCTION_ARGS)
 }
 
 /*
+ * postgresGetForeignRelWidth
+ *		Informs how many columns (including pseudo ones) are needed.
+ */
+static AttrNumber
+postgresGetForeignRelWidth(PlannerInfo *root,
+						   RelOptInfo *baserel,
+						   Relation foreignrel,
+						   bool inhparent,
+						   List *targetList)
+{
+	PostgresFdwPlanState *fpstate = palloc0(sizeof(PostgresFdwPlanState));
+
+	baserel->fdw_private = fpstate;
+
+	/* does rowid pseudo-column is required? */
+	fpstate->anum_rowid = get_pseudo_rowid_column(baserel, targetList);
+	if (fpstate->anum_rowid != InvalidAttrNumber)
+		return fpstate->anum_rowid;
+
+	return RelationGetNumberOfAttributes(foreignrel);
+}
+
+/*
  * postgresGetForeignRelSize
  *		Estimate # of rows and width of the result of the scan
  *
@@ -283,7 +350,7 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	 * We use PostgresFdwPlanState to pass various information to subsequent
 	 * functions.
 	 */
-	fpstate = palloc0(sizeof(PostgresFdwPlanState));
+	fpstate = baserel->fdw_private;
 	initStringInfo(&fpstate->sql);
 	sql = &fpstate->sql;
 
@@ -320,10 +387,9 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	 */
 	classifyConditions(root, baserel, &remote_conds, &param_conds,
 					   &local_conds);
-	deparseSimpleSql(sql, root, baserel, local_conds);
+	deparseSimpleSql(sql, root, baserel, local_conds, fpstate->anum_rowid);
 	if (list_length(remote_conds) > 0)
 		appendWhereClause(sql, true, remote_conds, root);
-	elog(DEBUG3, "Query SQL: %s", sql->data);
 
 	/*
 	 * If the table or the server is configured to use remote EXPLAIN, connect
@@ -337,10 +403,10 @@ postgresGetForeignRelSize(PlannerInfo *root,
 		PGconn		   *conn;
 
 		user = GetUserMapping(GetOuterUserId(), server->serverid);
-		conn = GetConnection(server, user, false);
+		conn = GetConnection(server, user, PGSQL_FDW_CONNTX_NONE);
 		get_remote_estimate(sql->data, conn, &rows, &width,
 							&startup_cost, &total_cost);
-		ReleaseConnection(conn);
+		ReleaseConnection(conn, false);
 
 		/*
 		 * Estimate selectivity of conditions which are not used in remote
@@ -391,7 +457,6 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	fpstate->width = width;
 	fpstate->table = table;
 	fpstate->server = server;
-	baserel->fdw_private = (void *) fpstate;
 }
 
 /*
@@ -592,7 +657,7 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 	table = GetForeignTable(relid);
 	server = GetForeignServer(table->serverid);
 	user = GetUserMapping(GetOuterUserId(), server->serverid);
-	conn = GetConnection(server, user, true);
+	conn = GetConnection(server, user, PGSQL_FDW_CONNTX_READ_ONLY);
 	festate->conn = conn;
 
 	/* Result will be filled in first Iterate call. */
@@ -724,7 +789,7 @@ postgresEndForeignScan(ForeignScanState *node)
 	 * end of the scan to make the lifespan of remote transaction same as the
 	 * local query.
 	 */
-	ReleaseConnection(festate->conn);
+	ReleaseConnection(festate->conn, false);
 	festate->conn = NULL;
 
 	/* Discard fetch results */
@@ -790,7 +855,7 @@ get_remote_estimate(const char *sql, PGconn *conn,
 		PQclear(res);
 
 		/* Release connection and let connection manager cleanup. */
-		ReleaseConnection(conn);
+		ReleaseConnection(conn, true);
 
 		PG_RE_THROW();
 	}
@@ -947,7 +1012,7 @@ execute_query(ForeignScanState *node)
 		PQclear(res);
 
 		/* Release connection and let connection manager cleanup. */
-		ReleaseConnection(conn);
+		ReleaseConnection(conn, true);
 
 		/* propagate error */
 		PG_RE_THROW();
@@ -1105,6 +1170,8 @@ postgres_fdw_error_callback(void *arg)
 
 	relname = get_rel_name(errpos->relid);
 	colname = get_attname(errpos->relid, errpos->cur_attno);
+	if (!colname)
+		colname = "pseudo-column";
 	errcontext("column %s of foreign table %s",
 			   quote_identifier(colname), quote_identifier(relname));
 }
@@ -1172,7 +1239,7 @@ postgresAcquireSampleRowsFunc(Relation relation, int elevel,
 	table = GetForeignTable(relation->rd_id);
 	server = GetForeignServer(table->serverid);
 	user = GetUserMapping(GetOuterUserId(), server->serverid);
-	conn = GetConnection(server, user, true);
+	conn = GetConnection(server, user, PGSQL_FDW_CONNTX_READ_ONLY);
 
 	/*
 	 * Acquire sample rows from the result set.
@@ -1239,13 +1306,13 @@ postgresAcquireSampleRowsFunc(Relation relation, int elevel,
 		PQclear(res);
 
 		/* Release connection and let connection manager cleanup. */
-		ReleaseConnection(conn);
+		ReleaseConnection(conn, true);
 
 		PG_RE_THROW();
 	}
 	PG_END_TRY();
 
-	ReleaseConnection(conn);
+	ReleaseConnection(conn, false);
 
 	/* We assume that we have no dead tuple. */
 	*totaldeadrows = 0.0;
@@ -1429,3 +1496,318 @@ analyze_row_processor(PGresult *res, PostgresAnalyzeState *astate, bool first)
 
 	return;
 }
+
+static List *
+postgresPlanForeignModify(PlannerInfo *root,
+						  ModifyTable *plan,
+						  Index resultRelaion,
+						  Plan *subplan)
+{
+	CmdType			operation = plan->operation;
+	StringInfoData	sql;
+
+	initStringInfo(&sql);
+
+	/*
+	 * XXX - In case of UPDATE or DELETE commands are quite "simple",
+	 * we will be able to execute raw UPDATE or DELETE statement at
+	 * the stage of scan, instead of combination SELECT ... FOR UPDATE
+	 * and either of UPDATE or DELETE commands.
+	 * It should be an idea of optimization in the future version.
+	 *
+	 * XXX - FOR UPDATE should be appended on the remote query of scan
+	 * stage to avoid unexpected concurrent update on the target rows.
+	 */
+	if (operation == CMD_UPDATE || operation == CMD_DELETE)
+	{
+		ForeignScan	   *fscan;
+		Value		   *select_sql;
+
+		fscan = lookup_foreign_scan_plan(subplan, resultRelaion);
+		if (!fscan)
+			elog(ERROR, "no underlying scan plan found in subplan tree");
+
+		select_sql = list_nth(fscan->fdw_private,
+							  FdwPrivateSelectSql);
+		appendStringInfo(&sql, "%s FOR UPDATE", strVal(select_sql));
+		strVal(select_sql) = pstrdup(sql.data);
+
+		resetStringInfo(&sql);
+	}
+
+	switch (operation)
+	{
+		case CMD_INSERT:
+			deparseInsertSql(&sql, root, resultRelaion);
+			elog(DEBUG3, "Remote INSERT query: %s", sql.data);
+			break;
+		case CMD_UPDATE:
+			deparseUpdateSql(&sql, root, resultRelaion);
+			elog(DEBUG3, "Remote UPDATE query: %s", sql.data);
+			break;
+		case CMD_DELETE:
+			deparseDeleteSql(&sql, root, resultRelaion);
+			elog(DEBUG3, "Remote DELETE query: %s", sql.data);
+			break;
+		default:
+			elog(ERROR, "unexpected operation: %d", (int) operation);
+	}
+	return list_make1(makeString(sql.data));
+}
+
+static void
+postgresBeginForeignModify(ModifyTableState *mtstate,
+						   ResultRelInfo *resultRelInfo,
+						   List *fdw_private,
+						   Plan *subplan,
+						   int eflags)
+{
+	PostgresFdwModifyState *fmstate;
+	CmdType			operation = mtstate->operation;
+	Relation		frel = resultRelInfo->ri_RelationDesc;
+	AttrNumber		n_params = RelationGetNumberOfAttributes(frel) + 1;
+	ForeignTable   *ftable;
+	ForeignServer  *fserver;
+	UserMapping	   *fuser;
+	Oid				out_func_oid;
+	bool			isvarlena;
+
+	/*
+	 * Construct PostgresFdwExecutionState
+	 */
+	fmstate = palloc0(sizeof(PostgresFdwExecutionState));
+
+	ftable = GetForeignTable(RelationGetRelid(frel));
+	fserver = GetForeignServer(ftable->serverid);
+	fuser = GetUserMapping(GetOuterUserId(), fserver->serverid);
+
+	fmstate->query = strVal(linitial(fdw_private));
+	fmstate->conn = GetConnection(fserver, fuser,
+								  PGSQL_FDW_CONNTX_READ_WRITE);
+	fmstate->p_name = NULL;
+	fmstate->p_types = palloc0(sizeof(Oid) * n_params);
+	fmstate->p_flinfo = palloc0(sizeof(FmgrInfo) * n_params);
+
+	/* 1st parameter should be ctid on UPDATE or DELETE */
+	if (operation == CMD_UPDATE || operation == CMD_DELETE)
+	{
+		fmstate->p_types[fmstate->p_nums] = TIDOID;
+		getTypeOutputInfo(TIDOID, &out_func_oid, &isvarlena);
+		fmgr_info(out_func_oid, &fmstate->p_flinfo[fmstate->p_nums]);
+		fmstate->p_nums++;
+	}
+	/* following parameters should be regular columns */
+	if (operation == CMD_UPDATE || operation == CMD_INSERT)
+	{
+		AttrNumber	index;
+
+		for (index=0; index < RelationGetNumberOfAttributes(frel); index++)
+		{
+			Form_pg_attribute	attr = RelationGetDescr(frel)->attrs[index];
+
+			if (attr->attisdropped)
+				continue;
+
+			fmstate->p_types[fmstate->p_nums] = attr->atttypid;
+			getTypeOutputInfo(attr->atttypid, &out_func_oid, &isvarlena);
+			fmgr_info(out_func_oid, &fmstate->p_flinfo[fmstate->p_nums]);
+			fmstate->p_nums++;
+		}
+	}
+	Assert(fmstate->p_nums <= n_params);
+	fmstate->es_query_cxt = mtstate->ps.state->es_query_cxt;
+	resultRelInfo->ri_fdw_state = fmstate;
+}
+
+static void
+prepare_foreign_modify(PostgresFdwModifyState *fmstate)
+{
+	static int	prep_id = 1;
+	char		prep_name[NAMEDATALEN];
+	PGresult   *res;
+
+	snprintf(prep_name, sizeof(prep_name),
+			 "pgsql_fdw_prep_%08x", prep_id++);
+
+	res = PQprepare(fmstate->conn,
+					prep_name,
+					fmstate->query,
+					fmstate->p_nums,
+					fmstate->p_types);
+	if (!res || PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		PQclear(res);
+		elog(ERROR, "could not prepare statement (%s): %s",
+			 fmstate->query, PQerrorMessage(fmstate->conn));
+	}
+	PQclear(res);
+
+	fmstate->p_name = MemoryContextStrdup(fmstate->es_query_cxt, prep_name);
+}
+
+static int
+setup_exec_prepared(ResultRelInfo *resultRelInfo,
+					const char *rowid, HeapTuple tuple,
+					const char *p_values[], int p_lengths[])
+{
+	PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state;
+	Relation	frel = resultRelInfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(frel);
+	int			i, p_idx = 0;
+
+	/* 1st parameter should be ctid */
+	if (rowid)
+	{
+		p_values[p_idx] = rowid;
+		p_lengths[p_idx] = strlen(rowid) + 1;
+		p_idx++;
+	}
+
+	/* following parameters are as TupleDesc */
+	if (HeapTupleIsValid(tuple))
+	{
+		for (i = 0; i < tupdesc->natts; i++)
+		{
+			Form_pg_attribute	attr = tupdesc->attrs[i];
+			Datum		value;
+			bool		isnull;
+
+			if (attr->attisdropped)
+				continue;
+
+			value = heap_getattr(tuple, attr->attnum, tupdesc, &isnull);
+			if (isnull)
+			{
+				p_values[p_idx] = NULL;
+				p_lengths[p_idx] = 0;
+			}
+			else
+			{
+				p_values[p_idx] =
+					OutputFunctionCall(&fmstate->p_flinfo[p_idx], value);
+				p_lengths[p_idx] = strlen(p_values[p_idx]) + 1;
+			}
+			p_idx++;
+		}
+	}
+	return p_idx;
+}
+
+static int
+postgresExecForeignInsert(ResultRelInfo *resultRelInfo,
+						  HeapTuple tuple)
+{
+	PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state;
+	const char	  **p_values  = alloca(sizeof(char *) * fmstate->p_nums);
+	int			   *p_lengths = alloca(sizeof(int) * fmstate->p_nums);
+	AttrNumber		nattrs;
+	PGresult	   *res;
+	int				n_rows;
+
+	if (!fmstate->p_name)
+		prepare_foreign_modify(fmstate);
+
+	nattrs = setup_exec_prepared(resultRelInfo,
+								 NULL, tuple,
+								 p_values, p_lengths);
+	Assert(fmstate->p_nums == nattrs);
+
+	res = PQexecPrepared(fmstate->conn,
+						 fmstate->p_name,
+						 nattrs,
+						 p_values,
+						 p_lengths,
+						 NULL, 0);
+	if (!res ||  PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		PQclear(res);
+		elog(ERROR, "could not execute prepared statement (%s): %s",
+			 fmstate->query, PQerrorMessage(fmstate->conn));
+    }
+	n_rows = atoi(PQcmdTuples(res));
+    PQclear(res);
+
+	return n_rows;
+}
+
+static int
+postgresExecForeignDelete(ResultRelInfo *resultRelInfo, Datum rowid)
+{
+	PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state;
+	const char	   *p_values[1];
+	int				p_lengths[1];
+	AttrNumber		nattrs;
+	PGresult	   *res;
+	int				n_rows;
+
+	if (!fmstate->p_name)
+		prepare_foreign_modify(fmstate);
+
+	nattrs = setup_exec_prepared(resultRelInfo,
+								 DatumGetCString(rowid), NULL,
+								 p_values, p_lengths);
+	Assert(fmstate->p_nums == nattrs);
+
+	res = PQexecPrepared(fmstate->conn,
+						 fmstate->p_name,
+						 nattrs,
+						 p_values,
+						 p_lengths,
+						 NULL, 0);
+	if (!res ||  PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		PQclear(res);
+		elog(ERROR, "could not execute prepared statement (%s): %s",
+			 fmstate->query, PQerrorMessage(fmstate->conn));
+    }
+	n_rows = atoi(PQcmdTuples(res));
+    PQclear(res);
+
+	return n_rows;
+}
+
+static int
+postgresExecForeignUpdate(ResultRelInfo *resultRelInfo,
+						  Datum rowid, HeapTuple tuple)
+{
+	PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state;
+	const char	  **p_values  = alloca(sizeof(char *) * (fmstate->p_nums + 1));
+	int			   *p_lengths = alloca(sizeof(int) * (fmstate->p_nums + 1));
+	AttrNumber		nattrs;
+	PGresult	   *res;
+	int				n_rows;
+
+	if (!fmstate->p_name)
+		prepare_foreign_modify(fmstate);
+
+	nattrs = setup_exec_prepared(resultRelInfo,
+								 DatumGetCString(rowid), tuple,
+								 p_values, p_lengths);
+	Assert(fmstate->p_nums == nattrs);
+
+	res = PQexecPrepared(fmstate->conn,
+						 fmstate->p_name,
+						 nattrs,
+						 p_values,
+						 p_lengths,
+						 NULL, 0);
+	if (!res ||  PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		PQclear(res);
+		elog(ERROR, "could not execute prepared statement (%s): %s",
+			 fmstate->query, PQerrorMessage(fmstate->conn));
+    }
+	n_rows = atoi(PQcmdTuples(res));
+    PQclear(res);
+
+	return n_rows;
+}
+
+static void
+postgresEndForeignModify(ResultRelInfo *resultRelInfo)
+{
+	PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state;
+
+	ReleaseConnection(fmstate->conn, false);
+	fmstate->conn = NULL;
+}
diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h
index b5cefb8..3756a0d 100644
--- a/contrib/postgres_fdw/postgres_fdw.h
+++ b/contrib/postgres_fdw/postgres_fdw.h
@@ -30,7 +30,8 @@ int GetFetchCountOption(ForeignTable *table, ForeignServer *server);
 void deparseSimpleSql(StringInfo buf,
 					  PlannerInfo *root,
 					  RelOptInfo *baserel,
-					  List *local_conds);
+					  List *local_conds,
+					  AttrNumber anum_rowid);
 void appendWhereClause(StringInfo buf,
 					   bool has_where,
 					   List *exprs,
@@ -41,5 +42,8 @@ void classifyConditions(PlannerInfo *root,
 						List **param_conds,
 						List **local_conds);
 void deparseAnalyzeSql(StringInfo buf, Relation rel);
+void deparseInsertSql(StringInfo buf, PlannerInfo *root, Index rtindex);
+void deparseUpdateSql(StringInfo buf, PlannerInfo *root, Index rtindex);
+void deparseDeleteSql(StringInfo buf, PlannerInfo *root, Index rtindex);
 
 #endif /* POSTGRESQL_FDW_H */
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 7845e70..70b09b4 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -300,6 +300,19 @@ ERROR OUT;          -- ERROR
 SELECT srvname FROM postgres_fdw_connections;
 
 -- ===================================================================
+-- test for writable foreign table stuff (PoC stage now)
+-- ===================================================================
+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 *;
+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 *;
+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;
+
+-- ===================================================================
 -- cleanup
 -- ===================================================================
 DROP OPERATOR === (int, int) CASCADE;
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index 2d604ed..a2ec974 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -89,6 +89,46 @@
 
     <para>
 <programlisting>
+AttrNumber
+GetForeignRelWidth(PlannerInfo *root,
+                   RelOptInfo *baserel,
+                   Relation foreignrel,
+                   bool inhparent,
+                   List *targetList);
+</programlisting>
+     Obtain width of the result set to be fetched on foreign table scan.
+     This is an optional handler, and called at the beginning of planning
+     for a query involving a foreign table; during construction of
+     <literal>RelOptInfo</>.
+     <literal>root</> is the planner's global information about the query;
+     <literal>baserel</> is the planner's information about this table under
+     construction; and <literal>foreignrel</> is <literal>Relation</>
+     descriptor of the foreign table.
+     <literal>inhparent</> is a boolean to show whether the relation is
+     inheritance parent, even though foreign table does not support table
+     inheritance right now. <literal>targetList</> is list of
+     <literal>TargetEntry</> to be returned from the (sub-)query being
+     currently focused on.
+    </para>
+
+    <para>
+     The result value of this function shall be assigned on the
+     <literal>baserel-&gt;max_attr</>; that means expected number of
+     columns being fetched on the foreign table scan.
+     It should not be smaller than number of regular columns in definition
+     of this foreign table. All we can do is expansion of this value to
+     acquire slots for some additional attributes; being called pseudo-
+     columns. A typical usage of pseudo-column is to move an identifier of
+     a particular remote row to be updated or deleted from scanning stage
+     to modifying stage when foreign table performs as result relation of
+     the query. In addition, it also allows off-load burden of complex
+     calculation into foreign computing resources, and replace it with
+     just a reference of its calculated result on the remote side, instead
+     of local calculation.
+   </para>
+
+    <para>
+<programlisting>
 void
 GetForeignRelSize (PlannerInfo *root,
                    RelOptInfo *baserel,
@@ -96,7 +136,8 @@ 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 involving a foreign table,
+     unless <literal>FixupGetForeignRelWidth</> is not configured.
      <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
@@ -315,6 +356,115 @@ AcquireSampleRowsFunc (Relation relation, int elevel,
     </para>
 
     <para>
+     If FDW driver supports writable foreign tables, it must implement both
+     of <literal>BeginForeignModify</> and <literal>EndForeignModify</> at
+     least. In addition, some or all of <literal>ExecForeignInsert</>,
+     <literal>ExecForeignUpdate</> and <literal>ExecForeignDelete</> need
+     to be implement according to capability of FDW.
+    </para>     
+
+    <para>
+<programlisting>
+List *
+PlanForeignModify(PlannerInfo *root,
+                  ModifyTable *plan,
+                  Index resultRelation,
+                  Plan *subplan);
+</programlisting>
+     It allows FDW drivers to construct its own private stuff relevant to
+     the foreign table to be modified. This private stuff should have
+     form of <literal>List *</> data, then it shall be delivered as
+     3rd argument of <literal>BeginForeignModify</> on execution stage.
+    </para>
+    <para>
+     <literal>root</> is the planner's global information about the query.
+     <literal>plan</> is the master plan to modify result relation according
+     to its command type. Please also pay attention <literal>ModifyTable</>
+     may have multiple result relations in the future revision, even though
+     we don't support inheritance feature on foreign tables.
+     <literal>resultRelation</> is an index towards result relation in the
+     range table entries, and <literal>subplan</> is the relevant scan plan;
+     that should be <literal>ForeignScan</> at <literal>UPDATE</> or
+     <literal>DELETE</>, so the driver can access private datum of scan stage
+     using this argument.
+    </para>
+
+    <para>
+<programlisting>
+void
+BeginForeignModify (ModifyTableState *mtstate,
+                    ResultRelInfo *resultRelInfo,
+                    List *fdw_private,
+                    Plan *subplan,
+                    int eflags);
+</programlisting>
+     It is invoked at beginning of foreign table modification, durting
+     execurot startup. This routine should perform any initialization
+     stuffs needed prior to actual table modifications, but not start
+     modifying the actual tuples. (That should be done for each call of
+     <function>ExecForeignInsert</>, <function>ExecForeignUpdate</> or
+     <function>ExecForeignDelete</>.)
+    </para>
+    <para>
+     The <structfield>ri_fdw_state</> field of <structname>ResultRelInfo</>
+     is reserved to store any private stuff relevant to this foreign table,
+     so it is assumed this routine handles any initialization stuff and
+     save its per-scan state on this field.
+     Please also pay attention <literal>ModifyTableState</> may have multiple
+     result relations in the future revision, even though we don't support
+     inheritance feature on foreign tables. <literal>resultRelInfo</> is
+     master information connected to this foreign table.
+     <literal>fdw_private</> is a private stuff constructed at
+     <literal>PlanForeignModify</>, and <literal>subplan</> is the relevant
+     scan plan of this table modification.
+    </para>
+
+    <para>
+<programlisting>
+int
+ExecForeignInsert (ResultRelInfo *resultRelInfo,
+                   HeapTuple tuple);
+</programlisting>
+     Insert the given tuple into backing storage behalf on the foreign table.
+     The supplied tuple is already formed according to the definition of
+     relation, thus, all the pseudo-columns are already filtered out.
+     It can return number of rows being inserted, usually 1.
+    </para>
+
+    <para>
+<programlisting>
+int
+ExecForeignDelete (ResultRelInfo *resultRelInfo,
+                   Datum rowid);
+</programlisting>
+     Delete the tuple being identified with <literal>rowid</> in the backing
+     storage behalf on the foreign table.
+     It can return number of rows being deleted, usually 1.
+    </para>
+
+    <para>
+<programlisting>
+int
+ExecForeignUpdate (ResultRelInfo *resultRelInfo,
+                   Datum rowid,
+                   HeapTuple tuple);
+</programlisting>
+     Update the tuple being identified with <literal>rowid</> in the backing
+     storage behalf on the foreign table, by the given tuple.
+     It can return number of rows being updated, usually 1.
+    </para>
+
+    <para>
+<programlisting>
+void
+EndForeignModify (ResultRelInfo *resultRelInfo);
+</programlisting>
+     End the modification 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>
      The <structname>FdwRoutine</> struct type is declared in
      <filename>src/include/foreign/fdwapi.h</>, which see for additional
      details.
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index dbd3755..ccc1f3a 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"
@@ -934,6 +935,7 @@ void
 CheckValidResultRel(Relation resultRel, CmdType operation)
 {
 	TriggerDesc *trigDesc = resultRel->trigdesc;
+	FdwRoutine	*fdwroutine;
 
 	switch (resultRel->rd_rel->relkind)
 	{
@@ -985,10 +987,34 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
 			}
 			break;
 		case RELKIND_FOREIGN_TABLE:
-			ereport(ERROR,
-					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("cannot change foreign table \"%s\"",
-							RelationGetRelationName(resultRel))));
+			fdwroutine = GetFdwRoutineByRelId(RelationGetRelid(resultRel));
+			switch (operation)
+			{
+				case CMD_INSERT:
+					if (!fdwroutine || !fdwroutine->ExecForeignInsert)
+						ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("cannot insert into foreign table \"%s\"",
+									RelationGetRelationName(resultRel))));
+					break;
+				case CMD_UPDATE:
+					if (!fdwroutine || !fdwroutine->ExecForeignUpdate)
+						ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("cannot update foreign table \"%s\"",
+									RelationGetRelationName(resultRel))));
+					break;
+				case CMD_DELETE:
+					if (!fdwroutine || !fdwroutine->ExecForeignDelete)
+						ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("cannot delete from foreign table \"%s\"",
+									RelationGetRelationName(resultRel))));
+					break;
+				default:
+					elog(ERROR, "unrecognized CmdType: %d", (int) operation);
+					break;
+			}
 			break;
 		default:
 			ereport(ERROR,
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 9204859..1ee626f 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -25,6 +25,7 @@
 #include "executor/executor.h"
 #include "executor/nodeForeignscan.h"
 #include "foreign/fdwapi.h"
+#include "nodes/nodeFuncs.h"
 #include "utils/rel.h"
 
 static TupleTableSlot *ForeignNext(ForeignScanState *node);
@@ -93,6 +94,133 @@ ExecForeignScan(ForeignScanState *node)
 					(ExecScanRecheckMtd) ForeignRecheck);
 }
 
+/*
+ * pseudo_column_walker
+ *
+ * helper routine of GetPseudoTupleDesc. It pulls Var nodes that reference
+ * pseudo columns from targetlis of the relation
+ */
+typedef struct
+{
+	Relation	relation;
+	Index		varno;
+	List	   *pcolumns;
+	AttrNumber	max_attno;
+} pseudo_column_walker_context;
+
+static bool
+pseudo_column_walker(Node *node, pseudo_column_walker_context *context)
+{
+	if (node == NULL)
+		return false;
+	if (IsA(node, Var))
+	{
+		Var		   *var = (Var *) node;
+		ListCell   *cell;
+
+		if (var->varno == context->varno && var->varlevelsup == 0 &&
+			var->varattno > RelationGetNumberOfAttributes(context->relation))
+		{
+			foreach (cell, context->pcolumns)
+			{
+				Var	   *temp = lfirst(cell);
+
+				if (temp->varattno == var->varattno)
+				{
+					if (!equal(var, temp))
+						elog(ERROR, "asymmetric pseudo column appeared");
+					break;
+				}
+			}
+			if (!cell)
+			{
+				context->pcolumns = lappend(context->pcolumns, var);
+				if (var->varattno > context->max_attno)
+					context->max_attno = var->varattno;
+			}
+		}
+		return false;
+	}
+
+	/* Should not find an unplanned subquery */
+	Assert(!IsA(node, Query));
+
+	return expression_tree_walker(node, pseudo_column_walker,
+								  (void *)context);
+}
+
+/*
+ * GetPseudoTupleDesc
+ *
+ * It generates TupleDesc structure including pseudo-columns if required.
+ */
+static TupleDesc
+GetPseudoTupleDesc(ForeignScan *node, Relation relation)
+{
+	pseudo_column_walker_context context;
+	List	   *target_list = node->scan.plan.targetlist;
+	TupleDesc	tupdesc;
+	AttrNumber	attno;
+	ListCell   *cell;
+	ListCell   *prev;
+	bool		hasoid;
+
+	context.relation = relation;
+	context.varno = node->scan.scanrelid;
+	context.pcolumns = NIL;
+	context.max_attno = -1;
+
+	pseudo_column_walker((Node *)target_list, (void *)&context);
+	Assert(context.max_attno > RelationGetNumberOfAttributes(relation));
+
+	hasoid = RelationGetForm(relation)->relhasoids;
+	tupdesc = CreateTemplateTupleDesc(context.max_attno, hasoid);
+
+	for (attno = 1; attno <= context.max_attno; attno++)
+	{
+		/* case of regular columns */
+		if (attno <= RelationGetNumberOfAttributes(relation))
+		{
+			memcpy(tupdesc->attrs[attno - 1],
+				   RelationGetDescr(relation)->attrs[attno - 1],
+				   ATTRIBUTE_FIXED_PART_SIZE);
+			continue;
+		}
+
+		/* case of pseudo columns */
+		prev = NULL;
+		foreach (cell, context.pcolumns)
+		{
+			Var	   *var = lfirst(cell);
+
+			if (var->varattno == attno)
+			{
+				char		namebuf[NAMEDATALEN];
+
+				snprintf(namebuf, sizeof(namebuf),
+						 "pseudo_column_%d", attno);
+
+				TupleDescInitEntry(tupdesc,
+								   attno,
+								   namebuf,
+								   var->vartype,
+								   var->vartypmod,
+								   0);
+				TupleDescInitEntryCollation(tupdesc,
+											attno,
+											var->varcollid);
+				context.pcolumns
+					= list_delete_cell(context.pcolumns, cell, prev);
+				break;
+			}
+			prev = cell;
+		}
+		if (!cell)
+			elog(ERROR, "pseudo column %d of %s not in target list",
+				 attno, RelationGetRelationName(relation));
+	}
+	return tupdesc;
+}
 
 /* ----------------------------------------------------------------
  *		ExecInitForeignScan
@@ -103,6 +231,7 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
 {
 	ForeignScanState *scanstate;
 	Relation	currentRelation;
+	TupleDesc	tupdesc;
 	FdwRoutine *fdwroutine;
 
 	/* check for unsupported flags */
@@ -149,7 +278,12 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
 	/*
 	 * get the scan type from the relation descriptor.
 	 */
-	ExecAssignScanType(&scanstate->ss, RelationGetDescr(currentRelation));
+	if (node->fsPseudoCol)
+		tupdesc = GetPseudoTupleDesc(node, currentRelation);
+	else
+		tupdesc = RelationGetDescr(currentRelation);
+
+	ExecAssignScanType(&scanstate->ss, tupdesc);
 
 	/*
 	 * Initialize result tuple type and projection info.
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d31015c..bf9421e 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"
@@ -170,6 +171,7 @@ ExecInsert(TupleTableSlot *slot,
 	Relation	resultRelationDesc;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
+	int			num_rows = 1;
 
 	/*
 	 * get the heap tuple out of the tuple table slot, making sure we have a
@@ -225,6 +227,14 @@ ExecInsert(TupleTableSlot *slot,
 
 		newId = InvalidOid;
 	}
+	else if (resultRelInfo->ri_fdwroutine)
+	{
+		FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+		num_rows = fdwroutine->ExecForeignInsert(resultRelInfo, tuple);
+
+		newId = InvalidOid;
+	}
 	else
 	{
 		/*
@@ -252,7 +262,7 @@ ExecInsert(TupleTableSlot *slot,
 
 	if (canSetTag)
 	{
-		(estate->es_processed)++;
+		(estate->es_processed) += num_rows;
 		estate->es_lastoid = newId;
 		setLastTid(&(tuple->t_self));
 	}
@@ -285,7 +295,7 @@ ExecInsert(TupleTableSlot *slot,
  * ----------------------------------------------------------------
  */
 static TupleTableSlot *
-ExecDelete(ItemPointer tupleid,
+ExecDelete(Datum rowid,
 		   HeapTupleHeader oldtuple,
 		   TupleTableSlot *planSlot,
 		   EPQState *epqstate,
@@ -296,6 +306,7 @@ ExecDelete(ItemPointer tupleid,
 	Relation	resultRelationDesc;
 	HTSU_Result result;
 	HeapUpdateFailureData hufd;
+	int			num_rows = 1;
 
 	/*
 	 * get information on the (current) result relation
@@ -310,7 +321,7 @@ ExecDelete(ItemPointer tupleid,
 		bool		dodelete;
 
 		dodelete = ExecBRDeleteTriggers(estate, epqstate, resultRelInfo,
-										tupleid);
+										(ItemPointer)DatumGetPointer(rowid));
 
 		if (!dodelete)			/* "do nothing" */
 			return NULL;
@@ -334,8 +345,16 @@ ExecDelete(ItemPointer tupleid,
 		if (!dodelete)			/* "do nothing" */
 			return NULL;
 	}
+	else if (resultRelInfo->ri_fdwroutine)
+	{
+		FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+		num_rows = fdwroutine->ExecForeignDelete(resultRelInfo, rowid);
+	}
 	else
 	{
+		ItemPointer	tupleid = (ItemPointer) DatumGetPointer(rowid);
+
 		/*
 		 * delete the tuple
 		 *
@@ -430,10 +449,11 @@ ldelete:;
 	}
 
 	if (canSetTag)
-		(estate->es_processed)++;
+		(estate->es_processed) += num_rows;
 
 	/* AFTER ROW DELETE Triggers */
-	ExecARDeleteTriggers(estate, resultRelInfo, tupleid);
+	ExecARDeleteTriggers(estate, resultRelInfo,
+						 (ItemPointer)DatumGetPointer(rowid));
 
 	/* Process RETURNING if present */
 	if (resultRelInfo->ri_projectReturning)
@@ -457,7 +477,8 @@ ldelete:;
 		}
 		else
 		{
-			deltuple.t_self = *tupleid;
+			ItemPointerCopy((ItemPointer)DatumGetPointer(rowid),
+							&deltuple.t_self);
 			if (!heap_fetch(resultRelationDesc, SnapshotAny,
 							&deltuple, &delbuffer, false, NULL))
 				elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
@@ -499,7 +520,7 @@ ldelete:;
  * ----------------------------------------------------------------
  */
 static TupleTableSlot *
-ExecUpdate(ItemPointer tupleid,
+ExecUpdate(Datum rowid,
 		   HeapTupleHeader oldtuple,
 		   TupleTableSlot *slot,
 		   TupleTableSlot *planSlot,
@@ -513,6 +534,7 @@ ExecUpdate(ItemPointer tupleid,
 	HTSU_Result result;
 	HeapUpdateFailureData hufd;
 	List	   *recheckIndexes = NIL;
+	int			num_rows = 1;
 
 	/*
 	 * abort the operation if not running transactions
@@ -537,7 +559,7 @@ ExecUpdate(ItemPointer tupleid,
 		resultRelInfo->ri_TrigDesc->trig_update_before_row)
 	{
 		slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
-									tupleid, slot);
+									(ItemPointer)DatumGetPointer(rowid), slot);
 
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
@@ -567,8 +589,16 @@ ExecUpdate(ItemPointer tupleid,
 		/* trigger might have changed tuple */
 		tuple = ExecMaterializeSlot(slot);
 	}
+	else if (resultRelInfo->ri_fdwroutine)
+	{
+		FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+		num_rows = fdwroutine->ExecForeignUpdate(resultRelInfo, rowid, tuple);
+	}
 	else
 	{
+		ItemPointer	tupleid = (ItemPointer) DatumGetPointer(rowid);
+
 		/*
 		 * Check the constraints of the tuple
 		 *
@@ -687,11 +717,12 @@ lreplace:;
 	}
 
 	if (canSetTag)
-		(estate->es_processed)++;
+		(estate->es_processed) += num_rows;
 
 	/* AFTER ROW UPDATE Triggers */
-	ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple,
-						 recheckIndexes);
+	ExecARUpdateTriggers(estate, resultRelInfo,
+						 (ItemPointer) DatumGetPointer(rowid),
+						 tuple, recheckIndexes);
 
 	list_free(recheckIndexes);
 
@@ -771,6 +802,7 @@ ExecModifyTable(ModifyTableState *node)
 	TupleTableSlot *planSlot;
 	ItemPointer tupleid = NULL;
 	ItemPointerData tuple_ctid;
+	Datum		rowid = 0;
 	HeapTupleHeader oldtuple = NULL;
 
 	/*
@@ -859,17 +891,19 @@ ExecModifyTable(ModifyTableState *node)
 		if (junkfilter != NULL)
 		{
 			/*
-			 * extract the 'ctid' or 'wholerow' junk attribute.
+			 * extract the 'ctid', 'rowid' or 'wholerow' junk attribute.
 			 */
 			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,
+												 junkfilter->jf_junkRowidNo,
 												 &isNull);
 					/* shouldn't ever get a null result... */
 					if (isNull)
@@ -877,13 +911,33 @@ ExecModifyTable(ModifyTableState *node)
 
 					tupleid = (ItemPointer) DatumGetPointer(datum);
 					tuple_ctid = *tupleid;		/* be sure we don't free
-												 * ctid!! */
-					tupleid = &tuple_ctid;
+												 * ctid ! */
+					rowid = PointerGetDatum(&tuple_ctid);
+				}
+				else if (relkind == RELKIND_FOREIGN_TABLE)
+				{
+					datum = ExecGetJunkAttribute(slot,
+												 junkfilter->jf_junkRowidNo,
+												 &isNull);
+					/* shouldn't ever get a null result... */
+					if (isNull)
+						elog(ERROR, "rowid is NULL");
+
+					rowid = datum;
+
+					datum = ExecGetJunkAttribute(slot,
+												 junkfilter->jf_junkRecordNo,
+												 &isNull);
+					/* shouldn't ever get a null result... */
+					if (isNull)
+						elog(ERROR, "wholerow is NULL");
+
+					oldtuple = DatumGetHeapTupleHeader(datum);
 				}
 				else
 				{
 					datum = ExecGetJunkAttribute(slot,
-												 junkfilter->jf_junkAttNo,
+												 junkfilter->jf_junkRecordNo,
 												 &isNull);
 					/* shouldn't ever get a null result... */
 					if (isNull)
@@ -906,11 +960,11 @@ ExecModifyTable(ModifyTableState *node)
 				slot = ExecInsert(slot, planSlot, estate, node->canSetTag);
 				break;
 			case CMD_UPDATE:
-				slot = ExecUpdate(tupleid, oldtuple, slot, planSlot,
+				slot = ExecUpdate(rowid, oldtuple, slot, planSlot,
 								&node->mt_epqstate, estate, node->canSetTag);
 				break;
 			case CMD_DELETE:
-				slot = ExecDelete(tupleid, oldtuple, planSlot,
+				slot = ExecDelete(rowid, oldtuple, planSlot,
 								&node->mt_epqstate, estate, node->canSetTag);
 				break;
 			default:
@@ -997,6 +1051,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	i = 0;
 	foreach(l, node->plans)
 	{
+		char	relkind;
+
 		subplan = (Plan *) lfirst(l);
 
 		/*
@@ -1022,6 +1078,24 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		estate->es_result_relation_info = resultRelInfo;
 		mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
 
+		/*
+		 * Also tells FDW extensions to init the plan for this result rel
+		 */
+		relkind = RelationGetForm(resultRelInfo->ri_RelationDesc)->relkind;
+		if (relkind == RELKIND_FOREIGN_TABLE)
+		{
+			Oid		relid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+			FdwRoutine *fdwroutine = GetFdwRoutineByRelId(relid);
+			List   *fdwprivate = list_nth(node->fdwPrivList, i);
+
+			Assert(fdwroutine != NULL);
+			resultRelInfo->ri_fdwroutine = fdwroutine;
+			resultRelInfo->ri_fdw_state = NULL;
+
+			if (fdwroutine->BeginForeignModify)
+				fdwroutine->BeginForeignModify(mtstate, resultRelInfo,
+											   fdwprivate, subplan, eflags);
+		}
 		resultRelInfo++;
 		i++;
 	}
@@ -1163,6 +1237,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			for (i = 0; i < nplans; i++)
 			{
 				JunkFilter *j;
+				char		relkind =
+				    RelationGetForm(resultRelInfo->ri_RelationDesc)->relkind;
 
 				subplan = mtstate->mt_plans[i]->plan;
 				if (operation == CMD_INSERT || operation == CMD_UPDATE)
@@ -1176,16 +1252,27 @@ 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)
+					if (relkind == RELKIND_RELATION)
 					{
-						j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
-						if (!AttributeNumberIsValid(j->jf_junkAttNo))
+						j->jf_junkRowidNo = ExecFindJunkAttribute(j, "ctid");
+						if (!AttributeNumberIsValid(j->jf_junkRowidNo))
 							elog(ERROR, "could not find junk ctid column");
 					}
+					else if (relkind == RELKIND_FOREIGN_TABLE)
+					{
+						j->jf_junkRowidNo = ExecFindJunkAttribute(j, "rowid");
+						if (!AttributeNumberIsValid(j->jf_junkRowidNo))
+							elog(ERROR, "could not find junk rowid column");
+						j->jf_junkRecordNo
+							= ExecFindJunkAttribute(j, "record");
+						if (!AttributeNumberIsValid(j->jf_junkRecordNo))
+							elog(ERROR, "could not find junk wholerow column");
+					}
 					else
 					{
-						j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
-						if (!AttributeNumberIsValid(j->jf_junkAttNo))
+						j->jf_junkRecordNo
+							= ExecFindJunkAttribute(j, "wholerow");
+						if (!AttributeNumberIsValid(j->jf_junkRecordNo))
 							elog(ERROR, "could not find junk wholerow column");
 					}
 				}
@@ -1239,6 +1326,21 @@ ExecEndModifyTable(ModifyTableState *node)
 {
 	int			i;
 
+	/* Let the FDW shut dowm */
+	for (i=0; i < node->ps.state->es_num_result_relations; i++)
+	{
+		ResultRelInfo  *resultRelInfo
+			= &node->ps.state->es_result_relations[i];
+
+		if (resultRelInfo->ri_fdwroutine)
+		{
+			FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+			if (fdwroutine->EndForeignModify)
+				fdwroutine->EndForeignModify(resultRelInfo);
+		}
+	}
+
 	/*
 	 * Free the exprcontext
 	 */
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
index d8845aa..04fd9a9 100644
--- a/src/backend/foreign/foreign.c
+++ b/src/backend/foreign/foreign.c
@@ -571,3 +571,71 @@ get_foreign_server_oid(const char *servername, bool missing_ok)
 				 errmsg("server \"%s\" does not exist", servername)));
 	return oid;
 }
+
+/*
+ * get_pseudo_rowid_column
+ *
+ * It picks up an attribute number to be used for pseudo rowid columns,
+ * if exists. It should be injected at rewriteHandler.c if supplied query
+ * is UPDATE or DELETE command. Elsewhere, it returns InvalidAttrNumber.
+ */
+AttrNumber
+get_pseudo_rowid_column(RelOptInfo *baserel, List *targetList)
+{
+	ListCell   *cell;
+
+	foreach (cell, targetList)
+	{
+		TargetEntry *tle = lfirst(cell);
+
+		if (tle->resjunk &&
+			tle->resname && strcmp(tle->resname, "rowid") == 0)
+		{
+			Var	   *var;
+
+			if (!IsA(tle->expr, Var))
+				elog(ERROR, "unexpected node on junk rowid entry: %d",
+					 (int) nodeTag(tle->expr));
+
+			var = (Var *) tle->expr;
+			if (baserel->relid == var->varno)
+				return var->varattno;
+		}
+	}
+	return InvalidAttrNumber;
+}
+
+/*
+ * lookup_foreign_scan_plan
+ *
+ * It looks up ForeignScan plan node with the supplied range-table id.
+ * Its typical usage is PlanForeignModify gets underlying scan plan on
+ * UPDATE or DELETE commands.
+ */
+ForeignScan *
+lookup_foreign_scan_plan(Plan *subplan, Index rtindex)
+{
+	if (!subplan)
+		return NULL;
+
+	else if (IsA(subplan, ForeignScan))
+	{
+		ForeignScan	   *fscan = (ForeignScan *) subplan;
+
+		if (fscan->scan.scanrelid == rtindex)
+			return fscan;
+	}
+	else
+	{
+		ForeignScan    *fscan;
+
+		fscan = lookup_foreign_scan_plan(subplan->lefttree, rtindex);
+		if (fscan != NULL)
+			return fscan;
+
+		fscan = lookup_foreign_scan_plan(subplan->righttree, rtindex);
+		if (fscan != NULL)
+			return fscan;
+	}
+	return NULL;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 9387ee9..5b1e8ac 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -181,6 +181,7 @@ _copyModifyTable(const ModifyTable *from)
 	COPY_NODE_FIELD(returningLists);
 	COPY_NODE_FIELD(rowMarks);
 	COPY_SCALAR_FIELD(epqParam);
+	COPY_NODE_FIELD(fdwPrivList);
 
 	return newnode;
 }
@@ -594,6 +595,7 @@ _copyForeignScan(const ForeignScan *from)
 	COPY_NODE_FIELD(fdw_exprs);
 	COPY_NODE_FIELD(fdw_private);
 	COPY_SCALAR_FIELD(fsSystemCol);
+	COPY_SCALAR_FIELD(fsPseudoCol);
 
 	return newnode;
 }
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 35c6287..c32ebd3 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -335,6 +335,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
 	WRITE_NODE_FIELD(returningLists);
 	WRITE_NODE_FIELD(rowMarks);
 	WRITE_INT_FIELD(epqParam);
+	WRITE_NODE_FIELD(fdwPrivList);
 }
 
 static void
@@ -562,6 +563,7 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
 	WRITE_NODE_FIELD(fdw_exprs);
 	WRITE_NODE_FIELD(fdw_private);
 	WRITE_BOOL_FIELD(fsSystemCol);
+	WRITE_BOOL_FIELD(fsPseudoCol);
 }
 
 static void
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 030f420..e9edb87 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -39,6 +39,7 @@
 #include "parser/parse_clause.h"
 #include "parser/parsetree.h"
 #include "utils/lsyscache.h"
+#include "utils/rel.h"
 
 
 static Plan *create_plan_recurse(PlannerInfo *root, Path *best_path);
@@ -1943,6 +1944,8 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 	RelOptInfo *rel = best_path->path.parent;
 	Index		scan_relid = rel->relid;
 	RangeTblEntry *rte;
+	Relation	relation;
+	AttrNumber	num_attrs;
 	int			i;
 
 	/* it should be a base rel... */
@@ -2001,6 +2004,22 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 		}
 	}
 
+	/*
+	 * Also, detect whether any pseudo columns are requested from rel.
+	 */
+	relation = heap_open(rte->relid, NoLock);
+	scan_plan->fsPseudoCol = false;
+	num_attrs = RelationGetNumberOfAttributes(relation);
+	for (i = num_attrs + 1; i <= rel->max_attr; i++)
+	{
+		if (!bms_is_empty(rel->attr_needed[i - rel->min_attr]))
+		{
+			scan_plan->fsPseudoCol = true;
+			break;
+		}
+	}
+	heap_close(relation, NoLock);
+
 	return scan_plan;
 }
 
@@ -4695,7 +4714,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)
@@ -4704,6 +4724,8 @@ make_modifytable(CmdType operation, bool canSetTag,
 	Plan	   *plan = &node->plan;
 	double		total_size;
 	ListCell   *subnode;
+	ListCell   *resultRel;
+	List	   *fdw_priv_list = NIL;
 
 	Assert(list_length(resultRelations) == list_length(subplans));
 	Assert(returningLists == NIL ||
@@ -4746,6 +4768,30 @@ make_modifytable(CmdType operation, bool canSetTag,
 	node->rowMarks = rowMarks;
 	node->epqParam = epqParam;
 
+	/*
+	 * Allows FDW driver to construct its private plan if result relation
+	 * is foreign table.
+	 */
+	forboth (resultRel, resultRelations, subnode, subplans)
+	{
+		RangeTblEntry  *rte = rt_fetch(lfirst_int(resultRel),
+									   root->parse->rtable);
+		List		   *fdw_private = NIL;
+		char			relkind = get_rel_relkind(rte->relid);
+
+		if (relkind == RELKIND_FOREIGN_TABLE)
+		{
+			FdwRoutine *fdwroutine = GetFdwRoutineByRelId(rte->relid);
+
+			if (fdwroutine && fdwroutine->PlanForeignModify)
+				fdw_private = fdwroutine->PlanForeignModify(root, node,
+													lfirst_int(resultRel),
+													(Plan *) lfirst(subnode));
+		}
+		fdw_priv_list = lappend(fdw_priv_list, fdw_private);
+	}
+	node->fdwPrivList = fdw_priv_list;
+
 	return node;
 }
 
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index bd719b5..57ba192 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -85,7 +85,7 @@ static void check_hashjoinable(RestrictInfo *restrictinfo);
  * "other rel" RelOptInfos for the members of any appendrels we find here.)
  */
 void
-add_base_rels_to_query(PlannerInfo *root, Node *jtnode)
+add_base_rels_to_query(PlannerInfo *root, List *tlist, Node *jtnode)
 {
 	if (jtnode == NULL)
 		return;
@@ -93,7 +93,7 @@ add_base_rels_to_query(PlannerInfo *root, Node *jtnode)
 	{
 		int			varno = ((RangeTblRef *) jtnode)->rtindex;
 
-		(void) build_simple_rel(root, varno, RELOPT_BASEREL);
+		(void) build_simple_rel(root, varno, tlist, RELOPT_BASEREL);
 	}
 	else if (IsA(jtnode, FromExpr))
 	{
@@ -101,14 +101,14 @@ add_base_rels_to_query(PlannerInfo *root, Node *jtnode)
 		ListCell   *l;
 
 		foreach(l, f->fromlist)
-			add_base_rels_to_query(root, lfirst(l));
+			add_base_rels_to_query(root, tlist, lfirst(l));
 	}
 	else if (IsA(jtnode, JoinExpr))
 	{
 		JoinExpr   *j = (JoinExpr *) jtnode;
 
-		add_base_rels_to_query(root, j->larg);
-		add_base_rels_to_query(root, j->rarg);
+		add_base_rels_to_query(root, tlist, j->larg);
+		add_base_rels_to_query(root, tlist, j->rarg);
 	}
 	else
 		elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
index c2488a4..4442444 100644
--- a/src/backend/optimizer/plan/planmain.c
+++ b/src/backend/optimizer/plan/planmain.c
@@ -112,6 +112,9 @@ query_planner(PlannerInfo *root, List *tlist,
 	 */
 	if (parse->jointree->fromlist == NIL)
 	{
+		/* Make a flattened version of the rangetable for faster access */
+		setup_simple_rel_arrays(root);
+
 		/* We need a trivial path result */
 		*cheapest_path = (Path *)
 			create_result_path((List *) parse->jointree->quals);
@@ -163,7 +166,7 @@ query_planner(PlannerInfo *root, List *tlist,
 	 * rangetable may contain RTEs for rels not actively part of the query,
 	 * for example views.  We don't want to make RelOptInfos for them.
 	 */
-	add_base_rels_to_query(root, (Node *) parse->jointree);
+	add_base_rels_to_query(root, tlist, (Node *) parse->jointree);
 
 	/*
 	 * Examine the targetlist and join tree, adding entries to baserel
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b61005f..2d72244 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,
@@ -3385,7 +3387,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 	setup_simple_rel_arrays(root);
 
 	/* Build RelOptInfo */
-	rel = build_simple_rel(root, 1, RELOPT_BASEREL);
+	rel = build_simple_rel(root, 1, NIL, RELOPT_BASEREL);
 
 	/* Locate IndexOptInfo for the target index */
 	indexInfo = NULL;
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index b91e9f4..61bc447 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -236,7 +236,8 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 		 * used for anything here, but it carries the subroot data structures
 		 * forward to setrefs.c processing.
 		 */
-		rel = build_simple_rel(root, rtr->rtindex, RELOPT_BASEREL);
+		rel = build_simple_rel(root, rtr->rtindex, refnames_tlist,
+							   RELOPT_BASEREL);
 
 		/* plan_params should not be in use in current query level */
 		Assert(root->plan_params == NIL);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 04d5028..b16cde3 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -25,6 +25,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/heap.h"
+#include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/clauses.h"
@@ -80,7 +81,7 @@ static List *build_index_tlist(PlannerInfo *root, IndexOptInfo *index,
  */
 void
 get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
-				  RelOptInfo *rel)
+				  List *tlist, RelOptInfo *rel)
 {
 	Index		varno = rel->relid;
 	Relation	relation;
@@ -104,6 +105,30 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 	rel->max_attr = RelationGetNumberOfAttributes(relation);
 	rel->reltablespace = RelationGetForm(relation)->reltablespace;
 
+	/*
+	 * Adjust width of attr_needed slot in case when FDW extension wants
+	 * or needs to return pseudo-columns also, not only columns in its
+	 * table definition.
+	 * GetForeignRelWidth, an optional FDW handler, enables FDW extension
+	 * to save properties of pseudo-column on its private field.
+	 * When foreign-table is the target of UPDATE/DELETE, query-rewritter
+	 * injects "rowid" pseudo-column to track remote row to be modified,
+	 * so FDW has to track which varattno shall perform as "rowid".
+	 */
+	if (RelationGetForm(relation)->relkind == RELKIND_FOREIGN_TABLE)
+	{
+		FdwRoutine *fdwroutine = GetFdwRoutineByRelId(relationObjectId);
+
+		if (fdwroutine->GetForeignRelWidth)
+		{
+			rel->max_attr = fdwroutine->GetForeignRelWidth(root, rel,
+														   relation,
+														   inhparent,
+														   tlist);
+			Assert(rel->max_attr >= RelationGetNumberOfAttributes(relation));
+		}
+	}
+
 	Assert(rel->max_attr >= rel->min_attr);
 	rel->attr_needed = (Relids *)
 		palloc0((rel->max_attr - rel->min_attr + 1) * sizeof(Relids));
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index f724714..84b674c 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -80,7 +80,8 @@ setup_simple_rel_arrays(PlannerInfo *root)
  *	  Construct a new RelOptInfo for a base relation or 'other' relation.
  */
 RelOptInfo *
-build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
+build_simple_rel(PlannerInfo *root, int relid, List *tlist,
+				 RelOptKind reloptkind)
 {
 	RelOptInfo *rel;
 	RangeTblEntry *rte;
@@ -133,7 +134,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
 	{
 		case RTE_RELATION:
 			/* Table --- retrieve statistics from the system catalogs */
-			get_relation_info(root, rte->relid, rte->inh, rel);
+			get_relation_info(root, rte->relid, rte->inh, tlist, rel);
 			break;
 		case RTE_SUBQUERY:
 		case RTE_FUNCTION:
@@ -180,7 +181,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
 			if (appinfo->parent_relid != relid)
 				continue;
 
-			(void) build_simple_rel(root, appinfo->child_relid,
+			(void) build_simple_rel(root, appinfo->child_relid, tlist,
 									RELOPT_OTHER_MEMBER_REL);
 		}
 	}
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index b785c26..6cd6241 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1165,7 +1165,10 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 					Relation target_relation)
 {
 	Var		   *var;
-	const char *attrname;
+	List	   *varList;
+	List	   *attNameList;
+	ListCell   *cell1;
+	ListCell   *cell2;
 	TargetEntry *tle;
 
 	if (target_relation->rd_rel->relkind == RELKIND_RELATION)
@@ -1179,8 +1182,35 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 					  -1,
 					  InvalidOid,
 					  0);
+		varList = list_make1(var);
+		attNameList = list_make1("ctid");
+	}
+	else if (target_relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+	{
+		/*
+		 * Emit Rowid so that executor can find the row to update or delete.
+		 */
+		var = makeVar(parsetree->resultRelation,
+					  RelationGetNumberOfAttributes(target_relation) + 1,
+					  CSTRINGOID,
+					  -2,
+					  InvalidOid,
+					  0);
+		varList = list_make1(var);
+
+		/*
+		 * Emit generic record Var so that executor will have the "old" view
+		 * row to pass the RETURNING clause (or upcoming triggers).
+		 */
+		var = makeVar(parsetree->resultRelation,
+					  InvalidAttrNumber,
+					  RECORDOID,
+					  -1,
+					  InvalidOid,
+					  0);
+		varList = lappend(varList, var);
 
-		attrname = "ctid";
+		attNameList = list_make2("rowid", "record");
 	}
 	else
 	{
@@ -1192,16 +1222,21 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 							  parsetree->resultRelation,
 							  0,
 							  false);
-
-		attrname = "wholerow";
+		varList = list_make1(var);
+		attNameList = list_make1("wholerow");
 	}
 
-	tle = makeTargetEntry((Expr *) var,
-						  list_length(parsetree->targetList) + 1,
-						  pstrdup(attrname),
-						  true);
-
-	parsetree->targetList = lappend(parsetree->targetList, tle);
+	/*
+	 * Append them to targetList
+	 */
+	forboth (cell1, varList, cell2, attNameList)
+	{
+		tle = makeTargetEntry((Expr *)lfirst(cell1),
+							  list_length(parsetree->targetList) + 1,
+							  pstrdup((const char *)lfirst(cell2)),
+							  true);
+		parsetree->targetList = lappend(parsetree->targetList, tle);
+	}
 }
 
 
diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
index 721cd25..153932a 100644
--- a/src/include/foreign/fdwapi.h
+++ b/src/include/foreign/fdwapi.h
@@ -14,6 +14,7 @@
 
 #include "nodes/execnodes.h"
 #include "nodes/relation.h"
+#include "utils/rel.h"
 
 /* To avoid including explain.h here, reference ExplainState thus: */
 struct ExplainState;
@@ -22,6 +23,11 @@ struct ExplainState;
 /*
  * Callback function signatures --- see fdwhandler.sgml for more info.
  */
+typedef AttrNumber (*GetForeignRelWidth_function) (PlannerInfo *root,
+												   RelOptInfo *baserel,
+												   Relation foreignrel,
+												   bool inhparent,
+												   List *targetList);
 
 typedef void (*GetForeignRelSize_function) (PlannerInfo *root,
 														RelOptInfo *baserel,
@@ -58,6 +64,24 @@ typedef int (*AcquireSampleRowsFunc) (Relation relation, int elevel,
 typedef bool (*AnalyzeForeignTable_function) (Relation relation,
 												 AcquireSampleRowsFunc *func,
 													BlockNumber *totalpages);
+typedef List *(*PlanForeignModify_function) (PlannerInfo *root,
+											 ModifyTable *plan,
+											 Index resultRelation,
+											 Plan *subplan);
+
+typedef void (*BeginForeignModify_function) (ModifyTableState *mtstate,
+											 ResultRelInfo *resultRelInfo,
+											 List *fdw_private,
+											 Plan *subplan,
+											 int eflags);
+typedef int	(*ExecForeignInsert_function) (ResultRelInfo *resultRelInfo,
+										   HeapTuple tuple);
+typedef int	(*ExecForeignDelete_function) (ResultRelInfo *resultRelInfo,
+										   Datum rowid);
+typedef int	(*ExecForeignUpdate_function) (ResultRelInfo *resultRelInfo,
+										   Datum rowid,
+										   HeapTuple tuple);
+typedef void (*EndForeignModify_function) (ResultRelInfo *resultRelInfo);
 
 /*
  * FdwRoutine is the struct returned by a foreign-data wrapper's handler
@@ -90,6 +114,13 @@ typedef struct FdwRoutine
 	 * not provided.
 	 */
 	AnalyzeForeignTable_function AnalyzeForeignTable;
+	GetForeignRelWidth_function GetForeignRelWidth;
+	PlanForeignModify_function PlanForeignModify;
+	BeginForeignModify_function	BeginForeignModify;
+	ExecForeignInsert_function ExecForeignInsert;
+	ExecForeignDelete_function ExecForeignDelete;
+	ExecForeignUpdate_function ExecForeignUpdate;
+	EndForeignModify_function EndForeignModify;
 } FdwRoutine;
 
 
diff --git a/src/include/foreign/foreign.h b/src/include/foreign/foreign.h
index f8aa99e..b63000a 100644
--- a/src/include/foreign/foreign.h
+++ b/src/include/foreign/foreign.h
@@ -14,6 +14,8 @@
 #define FOREIGN_H
 
 #include "nodes/parsenodes.h"
+#include "nodes/plannodes.h"
+#include "nodes/relation.h"
 
 
 /* Helper for obtaining username for user mapping */
@@ -81,4 +83,8 @@ extern List *GetForeignColumnOptions(Oid relid, AttrNumber attnum);
 extern Oid	get_foreign_data_wrapper_oid(const char *fdwname, bool missing_ok);
 extern Oid	get_foreign_server_oid(const char *servername, bool missing_ok);
 
+extern AttrNumber get_pseudo_rowid_column(RelOptInfo *baserel,
+										  List *targetList);
+extern ForeignScan *lookup_foreign_scan_plan(Plan *subplan,
+											 Index rtindex);
 #endif   /* FOREIGN_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index d4911bd..4c079b7 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -268,9 +268,11 @@ typedef struct ProjectionInfo
  *						attribute numbers of the "original" tuple and the
  *						attribute numbers of the "clean" tuple.
  *	  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
+ *	  jf_junkRowidNo:	not used by junkfilter code.  Can be used by caller
+ *						to remember the attno of to track a particular tuple
+ *						being updated or deleted.
  *						(execMain.c stores the "ctid" attno here).
+ *	  jf_junkRecordNo:	Also, the attno of whole-row reference.
  * ----------------
  */
 typedef struct JunkFilter
@@ -280,7 +282,8 @@ typedef struct JunkFilter
 	TupleDesc	jf_cleanTupType;
 	AttrNumber *jf_cleanMap;
 	TupleTableSlot *jf_resultSlot;
-	AttrNumber	jf_junkAttNo;
+	AttrNumber	jf_junkRowidNo;
+	AttrNumber	jf_junkRecordNo;
 } JunkFilter;
 
 /* ----------------
@@ -303,6 +306,8 @@ typedef struct JunkFilter
  *		ConstraintExprs			array of constraint-checking expr states
  *		junkFilter				for removing junk attributes from tuples
  *		projectReturning		for computing a RETURNING list
+ *		fdwroutine				FDW callbacks if foreign table
+ *		fdw_state				opaque state of FDW module, or NULL
  * ----------------
  */
 typedef struct ResultRelInfo
@@ -320,6 +325,8 @@ typedef struct ResultRelInfo
 	List	  **ri_ConstraintExprs;
 	JunkFilter *ri_junkFilter;
 	ProjectionInfo *ri_projectReturning;
+	struct FdwRoutine  *ri_fdwroutine;
+	void	   *ri_fdw_state;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index fb9a863..93481aa 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -175,6 +175,7 @@ typedef struct ModifyTable
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
 	int			epqParam;		/* ID of Param for EvalPlanQual re-eval */
+	List	   *fdwPrivList;	/* private fields for foreign tables */
 } ModifyTable;
 
 /* ----------------
@@ -478,6 +479,7 @@ typedef struct ForeignScan
 	List	   *fdw_exprs;		/* expressions that FDW may evaluate */
 	List	   *fdw_private;	/* private data for FDW */
 	bool		fsSystemCol;	/* true if any "system column" is needed */
+	bool		fsPseudoCol;	/* true if any "pseudo column" is needed */
 } ForeignScan;
 
 
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index de889fb..adfc93d 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -133,7 +133,7 @@ extern Path *reparameterize_path(PlannerInfo *root, Path *path,
  */
 extern void setup_simple_rel_arrays(PlannerInfo *root);
 extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid,
-				 RelOptKind reloptkind);
+			   List *tlist, RelOptKind reloptkind);
 extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid);
 extern RelOptInfo *find_join_rel(PlannerInfo *root, Relids relids);
 extern RelOptInfo *build_join_rel(PlannerInfo *root,
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index e0d04db..c5fce41 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -26,7 +26,7 @@ extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook;
 
 
 extern void get_relation_info(PlannerInfo *root, Oid relationObjectId,
-				  bool inhparent, RelOptInfo *rel);
+				  bool inhparent, List *tlist, RelOptInfo *rel);
 
 extern void estimate_rel_size(Relation rel, int32 *attr_widths,
 				  BlockNumber *pages, double *tuples, double *allvisfrac);
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 0fe696c..746a603 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);
@@ -90,7 +91,8 @@ extern bool is_projection_capable_plan(Plan *plan);
 extern int	from_collapse_limit;
 extern int	join_collapse_limit;
 
-extern void add_base_rels_to_query(PlannerInfo *root, Node *jtnode);
+extern void add_base_rels_to_query(PlannerInfo *root, List *tlist,
+								   Node *jtnode);
 extern void build_base_rel_tlists(PlannerInfo *root, List *final_tlist);
 extern void add_vars_to_targetlist(PlannerInfo *root, List *vars,
 					   Relids where_needed, bool create_new_ph);
#41Albe Laurenz
laurenz.albe@wien.gv.at
In reply to: Kohei KaiGai (#40)
1 attachment(s)
Re: [v9.3] writable foreign tables

Kohei KaiGai wrote:

Weird, that fails for me.

Both of the troubles you reported was fixed with attached patch.
I also added relevant test cases into regression test, please check it.

It passes the regression tests, and solves the problems I found.

I came up with one more query that causes a problem:

CREATE TABLE test(
id integer PRIMARY KEY,
val text NOT NULL
);

INSERT INTO test(id, val) VALUES (1, 'one');

CREATE FOREIGN TABLE rtest(
id integer not null,
val text not null
) SERVER loopback OPTIONS (relname 'test');

/* loopback points to the same database */

WITH ch AS (
UPDATE test
SET val='changed'
RETURNING id
) UPDATE rtest
SET val='new'
FROM ch
WHERE rtest.id = ch.id;

This causes a deadlock, but one that is not detected;
the query just keeps hanging.

The UPDATE in the CTE has the rows locked, so the
SELECT ... FOR UPDATE issued via the FDW connection will hang
indefinitely.

I wonder if that's just a pathological corner case that we shouldn't
worry about. Loopback connections for FDWs themselves might not
be so rare, for example as a substitute for autonomous subtransactions.

I guess it is not easily possible to detect such a situation or
to do something reasonable about it.

I took a brief look at the documentation; that will need some more
work.

Yep, I believe it should be revised prior to this patch being committed.

I tried to overhaul the documentation, see the attached patch.

There was one thing that I was not certain of:
You say that for writable foreign tables, BeginForeignModify
and EndForeignModify *must* be implemented.
I thought that these were optional, and if you can do your work
with just, say, ExecForeignDelete, you could do that.

I left that paragraph roughly as it is, please change it if
appropriate.

I also changed the misspelled "resultRelaion" and updated a
few comments.

May I suggest to split the patch in two parts, one for
all the parts that affect postgres_fdw and one for the rest?
That might make the committer's work easier, since
postgres_fdw is not applied (yet).

Yours,
Laurenz Albe

Attachments:

pgsql-v9.3-writable-fdw-poc.v7.patchapplication/octet-stream; name=pgsql-v9.3-writable-fdw-poc.v7.patchDownload
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
new file mode 100644
index eab8b87..9ca7fbf
*** a/contrib/postgres_fdw/connection.c
--- b/contrib/postgres_fdw/connection.c
*************** typedef struct ConnCacheEntry
*** 43,49 ****
  	Oid				serverid;	/* oid of foreign server */
  	Oid				userid;		/* oid of local user */
  
! 	bool			use_tx;		/* true when using remote transaction */
  	int				refs;		/* reference counter */
  	PGconn		   *conn;		/* foreign server connection */
  } ConnCacheEntry;
--- 43,49 ----
  	Oid				serverid;	/* oid of foreign server */
  	Oid				userid;		/* oid of local user */
  
! 	int				conntx;		/* one of PGSQL_FDW_CONNTX_* */
  	int				refs;		/* reference counter */
  	PGconn		   *conn;		/* foreign server connection */
  } ConnCacheEntry;
*************** cleanup_connection(ResourceReleasePhase 
*** 65,70 ****
--- 65,72 ----
  static PGconn *connect_pg_server(ForeignServer *server, UserMapping *user);
  static void begin_remote_tx(PGconn *conn);
  static void abort_remote_tx(PGconn *conn);
+ static void commit_remote_tx(PGconn *conn);
+ static void deallocate_remote_prepare(PGconn *conn);
  
  /*
   * Get a PGconn which can be used to execute foreign query on the remote
*************** static void abort_remote_tx(PGconn *conn
*** 80,86 ****
   * FDW object to invalidate already established connections.
   */
  PGconn *
! GetConnection(ForeignServer *server, UserMapping *user, bool use_tx)
  {
  	bool			found;
  	ConnCacheEntry *entry;
--- 82,88 ----
   * FDW object to invalidate already established connections.
   */
  PGconn *
! GetConnection(ForeignServer *server, UserMapping *user, int conntx)
  {
  	bool			found;
  	ConnCacheEntry *entry;
*************** GetConnection(ForeignServer *server, Use
*** 126,132 ****
  	entry = hash_search(ConnectionHash, &key, HASH_ENTER, &found);
  	if (!found)
  	{
! 		entry->use_tx = false;
  		entry->refs = 0;
  		entry->conn = NULL;
  	}
--- 128,134 ----
  	entry = hash_search(ConnectionHash, &key, HASH_ENTER, &found);
  	if (!found)
  	{
! 		entry->conntx = PGSQL_FDW_CONNTX_NONE;
  		entry->refs = 0;
  		entry->conn = NULL;
  	}
*************** GetConnection(ForeignServer *server, Use
*** 162,168 ****
  		{
  			/* Clear connection cache entry on error case. */
  			PQfinish(entry->conn);
! 			entry->use_tx = false;
  			entry->refs = 0;
  			entry->conn = NULL;
  			PG_RE_THROW();
--- 164,170 ----
  		{
  			/* Clear connection cache entry on error case. */
  			PQfinish(entry->conn);
! 			entry->conntx = PGSQL_FDW_CONNTX_NONE;
  			entry->refs = 0;
  			entry->conn = NULL;
  			PG_RE_THROW();
*************** GetConnection(ForeignServer *server, Use
*** 182,191 ****
  	 * are in.  We need to remember whether this connection uses remote
  	 * transaction to abort it when this connection is released completely.
  	 */
! 	if (use_tx && !entry->use_tx)
  	{
! 		begin_remote_tx(entry->conn);
! 		entry->use_tx = use_tx;
  	}
  
  	return entry->conn;
--- 184,194 ----
  	 * are in.  We need to remember whether this connection uses remote
  	 * transaction to abort it when this connection is released completely.
  	 */
! 	if (conntx > entry->conntx)
  	{
! 		if (entry->conntx == PGSQL_FDW_CONNTX_NONE)
! 			begin_remote_tx(entry->conn);
! 		entry->conntx = conntx;
  	}
  
  	return entry->conn;
*************** abort_remote_tx(PGconn *conn)
*** 355,366 ****
  	PQclear(res);
  }
  
  /*
   * Mark the connection as "unused", and close it if the caller was the last
   * user of the connection.
   */
  void
! ReleaseConnection(PGconn *conn)
  {
  	HASH_SEQ_STATUS		scan;
  	ConnCacheEntry	   *entry;
--- 358,402 ----
  	PQclear(res);
  }
  
+ static void
+ commit_remote_tx(PGconn *conn)
+ {
+ 	PGresult	   *res;
+ 
+ 	elog(DEBUG3, "committing remote transaction");
+ 
+ 	res = PQexec(conn, "COMMIT TRANSACTION");
+ 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ 	{
+ 		PQclear(res);
+ 		elog(ERROR, "could not commit transaction: %s", PQerrorMessage(conn));
+ 	}
+ 	PQclear(res);
+ }
+ 
+ static void
+ deallocate_remote_prepare(PGconn *conn)
+ {
+ 	PGresult	   *res;
+ 
+ 	elog(DEBUG3, "deallocating remote prepares");
+ 
+ 	res = PQexec(conn, "DEALLOCATE PREPARE ALL");
+ 	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ 	{
+ 		PQclear(res);
+ 		elog(ERROR, "could not deallocate prepared statement: %s",
+ 			 PQerrorMessage(conn));
+ 	}
+ 	PQclear(res);
+ }
+ 
  /*
   * Mark the connection as "unused", and close it if the caller was the last
   * user of the connection.
   */
  void
! ReleaseConnection(PGconn *conn, bool is_abort)
  {
  	HASH_SEQ_STATUS		scan;
  	ConnCacheEntry	   *entry;
*************** ReleaseConnection(PGconn *conn)
*** 412,418 ****
  			 PQtransactionStatus(conn) == PQTRANS_INERROR ? "INERROR" :
  			 "UNKNOWN");
  		PQfinish(conn);
! 		entry->use_tx = false;
  		entry->refs = 0;
  		entry->conn = NULL;
  		return;
--- 448,454 ----
  			 PQtransactionStatus(conn) == PQTRANS_INERROR ? "INERROR" :
  			 "UNKNOWN");
  		PQfinish(conn);
! 		entry->conntx = PGSQL_FDW_CONNTX_NONE;
  		entry->refs = 0;
  		entry->conn = NULL;
  		return;
*************** ReleaseConnection(PGconn *conn)
*** 430,439 ****
  	 * If this connection uses remote transaction and there is no user other
  	 * than the caller, abort the remote transaction and forget about it.
  	 */
! 	if (entry->use_tx && entry->refs == 0)
  	{
! 		abort_remote_tx(conn);
! 		entry->use_tx = false;
  	}
  }
  
--- 466,481 ----
  	 * If this connection uses remote transaction and there is no user other
  	 * than the caller, abort the remote transaction and forget about it.
  	 */
! 	if (entry->conntx > PGSQL_FDW_CONNTX_NONE && entry->refs == 0)
  	{
! 		if (entry->conntx > PGSQL_FDW_CONNTX_READ_ONLY)
! 			deallocate_remote_prepare(conn);
! 		if (is_abort || entry->conntx == PGSQL_FDW_CONNTX_READ_ONLY)
! 			abort_remote_tx(conn);
! 		else
! 			commit_remote_tx(conn);
! 
! 		entry->conntx = PGSQL_FDW_CONNTX_NONE;
  	}
  }
  
*************** cleanup_connection(ResourceReleasePhase 
*** 485,491 ****
  		elog(DEBUG3, "discard postgres_fdw connection %p due to resowner cleanup",
  			 entry->conn);
  		PQfinish(entry->conn);
! 		entry->use_tx = false;
  		entry->refs = 0;
  		entry->conn = NULL;
  	}
--- 527,533 ----
  		elog(DEBUG3, "discard postgres_fdw connection %p due to resowner cleanup",
  			 entry->conn);
  		PQfinish(entry->conn);
! 		entry->conntx = PGSQL_FDW_CONNTX_NONE;
  		entry->refs = 0;
  		entry->conn = NULL;
  	}
*************** postgres_fdw_disconnect(PG_FUNCTION_ARGS
*** 597,603 ****
  
  	/* Discard cached connection, and clear reference counter. */
  	PQfinish(entry->conn);
! 	entry->use_tx = false;
  	entry->refs = 0;
  	entry->conn = NULL;
  
--- 639,645 ----
  
  	/* Discard cached connection, and clear reference counter. */
  	PQfinish(entry->conn);
! 	entry->conntx = PGSQL_FDW_CONNTX_NONE;
  	entry->refs = 0;
  	entry->conn = NULL;
  
diff --git a/contrib/postgres_fdw/connection.h b/contrib/postgres_fdw/connection.h
new file mode 100644
index 4c9d850..f97cc8a
*** a/contrib/postgres_fdw/connection.h
--- b/contrib/postgres_fdw/connection.h
***************
*** 16,25 ****
  #include "foreign/foreign.h"
  #include "libpq-fe.h"
  
  /*
   * Connection management
   */
! PGconn *GetConnection(ForeignServer *server, UserMapping *user, bool use_tx);
! void ReleaseConnection(PGconn *conn);
  
  #endif /* CONNECTION_H */
--- 16,29 ----
  #include "foreign/foreign.h"
  #include "libpq-fe.h"
  
+ #define PGSQL_FDW_CONNTX_NONE			0
+ #define PGSQL_FDW_CONNTX_READ_ONLY		1
+ #define PGSQL_FDW_CONNTX_READ_WRITE		2
+ 
  /*
   * Connection management
   */
! PGconn *GetConnection(ForeignServer *server, UserMapping *user, int conntx);
! void ReleaseConnection(PGconn *conn, bool is_abort);
  
  #endif /* CONNECTION_H */
diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
new file mode 100644
index 69e6a3e..9e09429
*** a/contrib/postgres_fdw/deparse.c
--- b/contrib/postgres_fdw/deparse.c
***************
*** 12,17 ****
--- 12,18 ----
   */
  #include "postgres.h"
  
+ #include "access/heapam.h"
  #include "access/htup_details.h"
  #include "access/transam.h"
  #include "catalog/pg_class.h"
*************** void
*** 86,94 ****
  deparseSimpleSql(StringInfo buf,
  				 PlannerInfo *root,
  				 RelOptInfo *baserel,
! 				 List *local_conds)
  {
  	RangeTblEntry *rte;
  	ListCell   *lc;
  	StringInfoData	foreign_relname;
  	bool		first;
--- 87,97 ----
  deparseSimpleSql(StringInfo buf,
  				 PlannerInfo *root,
  				 RelOptInfo *baserel,
! 				 List *local_conds,
! 				 AttrNumber anum_rowid)
  {
  	RangeTblEntry *rte;
+ 	Relation	rel;
  	ListCell   *lc;
  	StringInfoData	foreign_relname;
  	bool		first;
*************** deparseSimpleSql(StringInfo buf,
*** 125,130 ****
--- 128,151 ----
  	}
  
  	/*
+ 	 * XXX - When this foreign table is target relation and RETURNING
+ 	 * clause reference some column, we have to mark these columns as
+ 	 * in-use. It is needed to support DELETE command, because INSERT
+ 	 * and UPDATE implicitly add references to all the regular columns
+ 	 * on baserel->reltargetlist.
+ 	 */
+ 	if (root->parse->resultRelation == baserel->relid &&
+ 		root->parse->returningList)
+ 	{
+ 		List   *attrs;
+ 
+ 		attrs = pull_var_clause((Node *) root->parse->returningList,
+ 								PVC_RECURSE_AGGREGATES,
+                                 PVC_RECURSE_PLACEHOLDERS);
+ 		attr_used = list_union(attr_used, attrs);
+ 	}
+ 
+ 	/*
  	 * deparse SELECT clause
  	 *
  	 * List attributes which are in either target list or local restriction.
*************** deparseSimpleSql(StringInfo buf,
*** 136,144 ****
  	 */
  	appendStringInfo(buf, "SELECT ");
  	rte = root->simple_rte_array[baserel->relid];
  	attr_used = list_union(attr_used, baserel->reltargetlist);
  	first = true;
! 	for (attr = 1; attr <= baserel->max_attr; attr++)
  	{
  		Var		   *var = NULL;
  		ListCell   *lc;
--- 157,166 ----
  	 */
  	appendStringInfo(buf, "SELECT ");
  	rte = root->simple_rte_array[baserel->relid];
+ 	rel = heap_open(rte->relid, NoLock);
  	attr_used = list_union(attr_used, baserel->reltargetlist);
  	first = true;
! 	for (attr = 1; attr <= RelationGetNumberOfAttributes(rel); attr++)
  	{
  		Var		   *var = NULL;
  		ListCell   *lc;
*************** deparseSimpleSql(StringInfo buf,
*** 167,172 ****
--- 189,198 ----
  		else
  			appendStringInfo(buf, "NULL");
  	}
+ 	if (anum_rowid != InvalidAttrNumber)
+ 		appendStringInfo(buf, "%sctid", (first ? "" : ","));
+ 
+ 	heap_close(rel, NoLock);
  	appendStringInfoChar(buf, ' ');
  
  	/*
*************** deparseAnalyzeSql(StringInfo buf, Relati
*** 283,288 ****
--- 309,410 ----
  }
  
  /*
+  * deparse remote INSERT statement
+  */
+ void
+ deparseInsertSql(StringInfo buf, PlannerInfo *root, Index rtindex)
+ {
+ 	RangeTblEntry  *rte = root->simple_rte_array[rtindex];
+ 	Relation		frel = heap_open(rte->relid, NoLock);
+ 	int				i, j, nattrs = RelationGetNumberOfAttributes(frel);
+ 	bool			is_first = true;
+ 
+ 	appendStringInfo(buf, "INSERT INTO ");
+ 	deparseRelation(buf, rte);
+ 	appendStringInfo(buf, "(");
+ 
+ 	for (i=0; i < nattrs; i++)
+ 	{
+ 		Form_pg_attribute attr = RelationGetDescr(frel)->attrs[i];
+ 		Var		var;
+ 
+ 		if (attr->attisdropped)
+ 			continue;
+ 
+ 		if (!is_first)
+ 			appendStringInfo(buf, ",");
+ 
+ 		var.varno = rtindex;
+ 		var.varattno = attr->attnum;
+ 		deparseVar(buf, &var, root);
+ 		is_first = false;
+ 	}
+ 	appendStringInfo(buf, ") VALUES (");
+ 
+ 	for (i=0, j=1; i < nattrs; i++)
+ 	{
+ 		Form_pg_attribute attr = RelationGetDescr(frel)->attrs[i];
+ 
+ 		if (attr->attisdropped)
+ 			continue;
+ 
+ 		appendStringInfo(buf, "%s$%d", (j == 1 ? "" : ","), j);
+ 		j++;
+ 	}
+ 	appendStringInfo(buf, ")");
+ 	heap_close(frel, NoLock);
+ }
+ 
+ /*
+  * deparse remote UPDATE statement
+  */
+ void
+ deparseUpdateSql(StringInfo buf, PlannerInfo *root, Index rtindex)
+ {
+ 	RangeTblEntry  *rte = root->simple_rte_array[rtindex];
+ 	Relation		frel = heap_open(rte->relid, NoLock);
+ 	int				i, j, nattrs = RelationGetNumberOfAttributes(frel);
+ 	bool			is_first = true;
+ 
+ 	appendStringInfo(buf, "UPDATE ");
+ 	deparseRelation(buf, rte);
+ 	appendStringInfo(buf, " SET ");
+ 
+ 	for (i=0, j=2; i < nattrs; i++)
+ 	{
+ 		Form_pg_attribute attr = RelationGetDescr(frel)->attrs[i];
+ 		Var		var;
+ 
+ 		if (attr->attisdropped)
+ 			continue;
+ 
+ 		if (!is_first)
+ 			appendStringInfo(buf, ",");
+ 
+ 		var.varno = rtindex;
+ 		var.varattno = attr->attnum;
+ 		deparseVar(buf, &var, root);
+ 		appendStringInfo(buf, "=$%d", j++);
+ 		is_first = false;
+ 	}
+ 	appendStringInfo(buf, " WHERE ctid=$1");
+ 	heap_close(frel, NoLock);
+ }
+ 
+ /*
+  * deparse remote DELETE statement
+  */
+ void
+ deparseDeleteSql(StringInfo buf, PlannerInfo *root, Index rtindex)
+ {
+ 	RangeTblEntry  *rte = root->simple_rte_array[rtindex];
+ 
+ 	appendStringInfo(buf, "DELETE FROM ");
+ 	deparseRelation(buf, rte);
+ 	appendStringInfo(buf, " WHERE ctid = $1");
+ }
+ 
+ /*
   * Deparse given expression into buf.  Actual string operation is delegated to
   * node-type-specific functions.
   *
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
new file mode 100644
index f81c727..db2c725
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
*************** SELECT srvname FROM postgres_fdw_connect
*** 736,741 ****
--- 736,1796 ----
  (0 rows)
  
  -- ===================================================================
+ -- test for writable foreign table stuff (PoC stage now)
+ -- ===================================================================
+ 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)
+ 
+ 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)
+ 
+ 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)
+ 
+ -- ===================================================================
  -- cleanup
  -- ===================================================================
  DROP OPERATOR === (int, int) CASCADE;
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
new file mode 100644
index 6b870ab..787ee42
*** a/contrib/postgres_fdw/postgres_fdw.c
--- b/contrib/postgres_fdw/postgres_fdw.c
*************** typedef struct PostgresFdwPlanState {
*** 59,64 ****
--- 59,65 ----
  	List		   *param_conds;
  	List		   *local_conds;
  	int				width;			/* obtained by remote EXPLAIN */
+ 	AttrNumber		anum_rowid;
  
  	/* Cached catalog information. */
  	ForeignTable   *table;
*************** typedef struct PostgresAnalyzeState
*** 150,155 ****
--- 151,170 ----
  } PostgresAnalyzeState;
  
  /*
+  * Describes a state of modify request for a foreign table
+  */
+ typedef struct PostgresFdwModifyState
+ {
+ 	PGconn	   *conn;
+ 	char	   *query;
+ 	char	   *p_name;
+ 	int			p_nums;
+ 	Oid		   *p_types;
+ 	FmgrInfo   *p_flinfo;
+ 	MemoryContext	es_query_cxt;
+ } PostgresFdwModifyState;
+ 
+ /*
   * SQL functions
   */
  extern Datum postgres_fdw_handler(PG_FUNCTION_ARGS);
*************** PG_FUNCTION_INFO_V1(postgres_fdw_handler
*** 158,163 ****
--- 173,183 ----
  /*
   * FDW callback routines
   */
+ static AttrNumber postgresGetForeignRelWidth(PlannerInfo *root,
+ 											 RelOptInfo *baserel,
+ 											 Relation foreignrel,
+ 											 bool inhparent,
+ 											 List *targetList);
  static void postgresGetForeignRelSize(PlannerInfo *root,
  									  RelOptInfo *baserel,
  									  Oid foreigntableid);
*************** static void postgresEndForeignScan(Forei
*** 179,184 ****
--- 199,221 ----
  static bool postgresAnalyzeForeignTable(Relation relation,
  										AcquireSampleRowsFunc *func,
  										BlockNumber *totalpages);
+ static List *postgresPlanForeignModify(PlannerInfo *root,
+ 									   ModifyTable *plan,
+ 									   Index resultRelation,
+ 									   Plan *subplan);
+ static void postgresBeginForeignModify(ModifyTableState *mtstate,
+ 									   ResultRelInfo *resultRelInfo,
+ 									   List *fdw_private,
+ 									   Plan *subplan,
+ 									   int eflags);
+ static int postgresExecForeignInsert(ResultRelInfo *resultRelInfo,
+ 									 HeapTuple tuple);
+ static int postgresExecForeignDelete(ResultRelInfo *resultRelInfo,
+ 									 Datum rowid);
+ static int postgresExecForeignUpdate(ResultRelInfo *resultRelInfo,
+ 									 Datum rowid,
+ 									 HeapTuple tuple);
+ static void postgresEndForeignModify(ResultRelInfo *resultRelInfo);
  
  /*
   * Helper functions
*************** postgres_fdw_handler(PG_FUNCTION_ARGS)
*** 231,236 ****
--- 268,274 ----
  	FdwRoutine	*routine = makeNode(FdwRoutine);
  
  	/* Required handler functions. */
+ 	routine->GetForeignRelWidth = postgresGetForeignRelWidth;
  	routine->GetForeignRelSize = postgresGetForeignRelSize;
  	routine->GetForeignPaths = postgresGetForeignPaths;
  	routine->GetForeignPlan = postgresGetForeignPlan;
*************** postgres_fdw_handler(PG_FUNCTION_ARGS)
*** 239,244 ****
--- 277,288 ----
  	routine->IterateForeignScan = postgresIterateForeignScan;
  	routine->ReScanForeignScan = postgresReScanForeignScan;
  	routine->EndForeignScan = postgresEndForeignScan;
+ 	routine->PlanForeignModify = postgresPlanForeignModify;
+ 	routine->BeginForeignModify = postgresBeginForeignModify;
+ 	routine->ExecForeignInsert = postgresExecForeignInsert;
+ 	routine->ExecForeignDelete = postgresExecForeignDelete;
+ 	routine->ExecForeignUpdate = postgresExecForeignUpdate;
+ 	routine->EndForeignModify = postgresEndForeignModify;
  
  	/* Optional handler functions. */
  	routine->AnalyzeForeignTable = postgresAnalyzeForeignTable;
*************** postgres_fdw_handler(PG_FUNCTION_ARGS)
*** 247,252 ****
--- 291,319 ----
  }
  
  /*
+  * postgresGetForeignRelWidth
+  *		Informs how many columns (including pseudo ones) are needed.
+  */
+ static AttrNumber
+ postgresGetForeignRelWidth(PlannerInfo *root,
+ 						   RelOptInfo *baserel,
+ 						   Relation foreignrel,
+ 						   bool inhparent,
+ 						   List *targetList)
+ {
+ 	PostgresFdwPlanState *fpstate = palloc0(sizeof(PostgresFdwPlanState));
+ 
+ 	baserel->fdw_private = fpstate;
+ 
+ 	/* does rowid pseudo-column is required? */
+ 	fpstate->anum_rowid = get_pseudo_rowid_column(baserel, targetList);
+ 	if (fpstate->anum_rowid != InvalidAttrNumber)
+ 		return fpstate->anum_rowid;
+ 
+ 	return RelationGetNumberOfAttributes(foreignrel);
+ }
+ 
+ /*
   * postgresGetForeignRelSize
   *		Estimate # of rows and width of the result of the scan
   *
*************** postgresGetForeignRelSize(PlannerInfo *r
*** 283,289 ****
  	 * We use PostgresFdwPlanState to pass various information to subsequent
  	 * functions.
  	 */
! 	fpstate = palloc0(sizeof(PostgresFdwPlanState));
  	initStringInfo(&fpstate->sql);
  	sql = &fpstate->sql;
  
--- 350,356 ----
  	 * We use PostgresFdwPlanState to pass various information to subsequent
  	 * functions.
  	 */
! 	fpstate = baserel->fdw_private;
  	initStringInfo(&fpstate->sql);
  	sql = &fpstate->sql;
  
*************** postgresGetForeignRelSize(PlannerInfo *r
*** 320,329 ****
  	 */
  	classifyConditions(root, baserel, &remote_conds, &param_conds,
  					   &local_conds);
! 	deparseSimpleSql(sql, root, baserel, local_conds);
  	if (list_length(remote_conds) > 0)
  		appendWhereClause(sql, true, remote_conds, root);
- 	elog(DEBUG3, "Query SQL: %s", sql->data);
  
  	/*
  	 * If the table or the server is configured to use remote EXPLAIN, connect
--- 387,395 ----
  	 */
  	classifyConditions(root, baserel, &remote_conds, &param_conds,
  					   &local_conds);
! 	deparseSimpleSql(sql, root, baserel, local_conds, fpstate->anum_rowid);
  	if (list_length(remote_conds) > 0)
  		appendWhereClause(sql, true, remote_conds, root);
  
  	/*
  	 * If the table or the server is configured to use remote EXPLAIN, connect
*************** postgresGetForeignRelSize(PlannerInfo *r
*** 337,346 ****
  		PGconn		   *conn;
  
  		user = GetUserMapping(GetOuterUserId(), server->serverid);
! 		conn = GetConnection(server, user, false);
  		get_remote_estimate(sql->data, conn, &rows, &width,
  							&startup_cost, &total_cost);
! 		ReleaseConnection(conn);
  
  		/*
  		 * Estimate selectivity of conditions which are not used in remote
--- 403,412 ----
  		PGconn		   *conn;
  
  		user = GetUserMapping(GetOuterUserId(), server->serverid);
! 		conn = GetConnection(server, user, PGSQL_FDW_CONNTX_NONE);
  		get_remote_estimate(sql->data, conn, &rows, &width,
  							&startup_cost, &total_cost);
! 		ReleaseConnection(conn, false);
  
  		/*
  		 * Estimate selectivity of conditions which are not used in remote
*************** postgresGetForeignRelSize(PlannerInfo *r
*** 391,397 ****
  	fpstate->width = width;
  	fpstate->table = table;
  	fpstate->server = server;
- 	baserel->fdw_private = (void *) fpstate;
  }
  
  /*
--- 457,462 ----
*************** postgresBeginForeignScan(ForeignScanStat
*** 592,598 ****
  	table = GetForeignTable(relid);
  	server = GetForeignServer(table->serverid);
  	user = GetUserMapping(GetOuterUserId(), server->serverid);
! 	conn = GetConnection(server, user, true);
  	festate->conn = conn;
  
  	/* Result will be filled in first Iterate call. */
--- 657,663 ----
  	table = GetForeignTable(relid);
  	server = GetForeignServer(table->serverid);
  	user = GetUserMapping(GetOuterUserId(), server->serverid);
! 	conn = GetConnection(server, user, PGSQL_FDW_CONNTX_READ_ONLY);
  	festate->conn = conn;
  
  	/* Result will be filled in first Iterate call. */
*************** postgresEndForeignScan(ForeignScanState 
*** 724,730 ****
  	 * end of the scan to make the lifespan of remote transaction same as the
  	 * local query.
  	 */
! 	ReleaseConnection(festate->conn);
  	festate->conn = NULL;
  
  	/* Discard fetch results */
--- 789,795 ----
  	 * end of the scan to make the lifespan of remote transaction same as the
  	 * local query.
  	 */
! 	ReleaseConnection(festate->conn, false);
  	festate->conn = NULL;
  
  	/* Discard fetch results */
*************** get_remote_estimate(const char *sql, PGc
*** 790,796 ****
  		PQclear(res);
  
  		/* Release connection and let connection manager cleanup. */
! 		ReleaseConnection(conn);
  
  		PG_RE_THROW();
  	}
--- 855,861 ----
  		PQclear(res);
  
  		/* Release connection and let connection manager cleanup. */
! 		ReleaseConnection(conn, true);
  
  		PG_RE_THROW();
  	}
*************** execute_query(ForeignScanState *node)
*** 947,953 ****
  		PQclear(res);
  
  		/* Release connection and let connection manager cleanup. */
! 		ReleaseConnection(conn);
  
  		/* propagate error */
  		PG_RE_THROW();
--- 1012,1018 ----
  		PQclear(res);
  
  		/* Release connection and let connection manager cleanup. */
! 		ReleaseConnection(conn, true);
  
  		/* propagate error */
  		PG_RE_THROW();
*************** postgres_fdw_error_callback(void *arg)
*** 1105,1110 ****
--- 1170,1177 ----
  
  	relname = get_rel_name(errpos->relid);
  	colname = get_attname(errpos->relid, errpos->cur_attno);
+ 	if (!colname)
+ 		colname = "pseudo-column";
  	errcontext("column %s of foreign table %s",
  			   quote_identifier(colname), quote_identifier(relname));
  }
*************** postgresAcquireSampleRowsFunc(Relation r
*** 1172,1178 ****
  	table = GetForeignTable(relation->rd_id);
  	server = GetForeignServer(table->serverid);
  	user = GetUserMapping(GetOuterUserId(), server->serverid);
! 	conn = GetConnection(server, user, true);
  
  	/*
  	 * Acquire sample rows from the result set.
--- 1239,1245 ----
  	table = GetForeignTable(relation->rd_id);
  	server = GetForeignServer(table->serverid);
  	user = GetUserMapping(GetOuterUserId(), server->serverid);
! 	conn = GetConnection(server, user, PGSQL_FDW_CONNTX_READ_ONLY);
  
  	/*
  	 * Acquire sample rows from the result set.
*************** postgresAcquireSampleRowsFunc(Relation r
*** 1239,1251 ****
  		PQclear(res);
  
  		/* Release connection and let connection manager cleanup. */
! 		ReleaseConnection(conn);
  
  		PG_RE_THROW();
  	}
  	PG_END_TRY();
  
! 	ReleaseConnection(conn);
  
  	/* We assume that we have no dead tuple. */
  	*totaldeadrows = 0.0;
--- 1306,1318 ----
  		PQclear(res);
  
  		/* Release connection and let connection manager cleanup. */
! 		ReleaseConnection(conn, true);
  
  		PG_RE_THROW();
  	}
  	PG_END_TRY();
  
! 	ReleaseConnection(conn, false);
  
  	/* We assume that we have no dead tuple. */
  	*totaldeadrows = 0.0;
*************** analyze_row_processor(PGresult *res, Pos
*** 1429,1431 ****
--- 1496,1813 ----
  
  	return;
  }
+ 
+ static List *
+ postgresPlanForeignModify(PlannerInfo *root,
+ 						  ModifyTable *plan,
+ 						  Index resultRelation,
+ 						  Plan *subplan)
+ {
+ 	CmdType			operation = plan->operation;
+ 	StringInfoData	sql;
+ 
+ 	initStringInfo(&sql);
+ 
+ 	/*
+ 	 * XXX - In case of UPDATE or DELETE commands are quite "simple",
+ 	 * we will be able to execute raw UPDATE or DELETE statement at
+ 	 * the stage of scan, instead of combination SELECT ... FOR UPDATE
+ 	 * and either of UPDATE or DELETE commands.
+ 	 * It should be an idea of optimization in the future version.
+ 	 *
+ 	 * XXX - FOR UPDATE should be appended on the remote query of scan
+ 	 * stage to avoid unexpected concurrent update on the target rows.
+ 	 */
+ 	if (operation == CMD_UPDATE || operation == CMD_DELETE)
+ 	{
+ 		ForeignScan	   *fscan;
+ 		Value		   *select_sql;
+ 
+ 		fscan = lookup_foreign_scan_plan(subplan, resultRelation);
+ 		if (!fscan)
+ 			elog(ERROR, "no underlying scan plan found in subplan tree");
+ 
+ 		select_sql = list_nth(fscan->fdw_private,
+ 							  FdwPrivateSelectSql);
+ 		appendStringInfo(&sql, "%s FOR UPDATE", strVal(select_sql));
+ 		strVal(select_sql) = pstrdup(sql.data);
+ 
+ 		resetStringInfo(&sql);
+ 	}
+ 
+ 	switch (operation)
+ 	{
+ 		case CMD_INSERT:
+ 			deparseInsertSql(&sql, root, resultRelation);
+ 			elog(DEBUG3, "Remote INSERT query: %s", sql.data);
+ 			break;
+ 		case CMD_UPDATE:
+ 			deparseUpdateSql(&sql, root, resultRelation);
+ 			elog(DEBUG3, "Remote UPDATE query: %s", sql.data);
+ 			break;
+ 		case CMD_DELETE:
+ 			deparseDeleteSql(&sql, root, resultRelation);
+ 			elog(DEBUG3, "Remote DELETE query: %s", sql.data);
+ 			break;
+ 		default:
+ 			elog(ERROR, "unexpected operation: %d", (int) operation);
+ 	}
+ 	return list_make1(makeString(sql.data));
+ }
+ 
+ static void
+ postgresBeginForeignModify(ModifyTableState *mtstate,
+ 						   ResultRelInfo *resultRelInfo,
+ 						   List *fdw_private,
+ 						   Plan *subplan,
+ 						   int eflags)
+ {
+ 	PostgresFdwModifyState *fmstate;
+ 	CmdType			operation = mtstate->operation;
+ 	Relation		frel = resultRelInfo->ri_RelationDesc;
+ 	AttrNumber		n_params = RelationGetNumberOfAttributes(frel) + 1;
+ 	ForeignTable   *ftable;
+ 	ForeignServer  *fserver;
+ 	UserMapping	   *fuser;
+ 	Oid				out_func_oid;
+ 	bool			isvarlena;
+ 
+ 	/*
+ 	 * Construct PostgresFdwExecutionState
+ 	 */
+ 	fmstate = palloc0(sizeof(PostgresFdwExecutionState));
+ 
+ 	ftable = GetForeignTable(RelationGetRelid(frel));
+ 	fserver = GetForeignServer(ftable->serverid);
+ 	fuser = GetUserMapping(GetOuterUserId(), fserver->serverid);
+ 
+ 	fmstate->query = strVal(linitial(fdw_private));
+ 	fmstate->conn = GetConnection(fserver, fuser,
+ 								  PGSQL_FDW_CONNTX_READ_WRITE);
+ 	fmstate->p_name = NULL;
+ 	fmstate->p_types = palloc0(sizeof(Oid) * n_params);
+ 	fmstate->p_flinfo = palloc0(sizeof(FmgrInfo) * n_params);
+ 
+ 	/* 1st parameter should be ctid on UPDATE or DELETE */
+ 	if (operation == CMD_UPDATE || operation == CMD_DELETE)
+ 	{
+ 		fmstate->p_types[fmstate->p_nums] = TIDOID;
+ 		getTypeOutputInfo(TIDOID, &out_func_oid, &isvarlena);
+ 		fmgr_info(out_func_oid, &fmstate->p_flinfo[fmstate->p_nums]);
+ 		fmstate->p_nums++;
+ 	}
+ 	/* following parameters should be regular columns */
+ 	if (operation == CMD_UPDATE || operation == CMD_INSERT)
+ 	{
+ 		AttrNumber	index;
+ 
+ 		for (index=0; index < RelationGetNumberOfAttributes(frel); index++)
+ 		{
+ 			Form_pg_attribute	attr = RelationGetDescr(frel)->attrs[index];
+ 
+ 			if (attr->attisdropped)
+ 				continue;
+ 
+ 			fmstate->p_types[fmstate->p_nums] = attr->atttypid;
+ 			getTypeOutputInfo(attr->atttypid, &out_func_oid, &isvarlena);
+ 			fmgr_info(out_func_oid, &fmstate->p_flinfo[fmstate->p_nums]);
+ 			fmstate->p_nums++;
+ 		}
+ 	}
+ 	Assert(fmstate->p_nums <= n_params);
+ 	fmstate->es_query_cxt = mtstate->ps.state->es_query_cxt;
+ 	resultRelInfo->ri_fdw_state = fmstate;
+ }
+ 
+ static void
+ prepare_foreign_modify(PostgresFdwModifyState *fmstate)
+ {
+ 	static int	prep_id = 1;
+ 	char		prep_name[NAMEDATALEN];
+ 	PGresult   *res;
+ 
+ 	snprintf(prep_name, sizeof(prep_name),
+ 			 "pgsql_fdw_prep_%08x", prep_id++);
+ 
+ 	res = PQprepare(fmstate->conn,
+ 					prep_name,
+ 					fmstate->query,
+ 					fmstate->p_nums,
+ 					fmstate->p_types);
+ 	if (!res || PQresultStatus(res) != PGRES_COMMAND_OK)
+ 	{
+ 		PQclear(res);
+ 		elog(ERROR, "could not prepare statement (%s): %s",
+ 			 fmstate->query, PQerrorMessage(fmstate->conn));
+ 	}
+ 	PQclear(res);
+ 
+ 	fmstate->p_name = MemoryContextStrdup(fmstate->es_query_cxt, prep_name);
+ }
+ 
+ static int
+ setup_exec_prepared(ResultRelInfo *resultRelInfo,
+ 					const char *rowid, HeapTuple tuple,
+ 					const char *p_values[], int p_lengths[])
+ {
+ 	PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state;
+ 	Relation	frel = resultRelInfo->ri_RelationDesc;
+ 	TupleDesc	tupdesc = RelationGetDescr(frel);
+ 	int			i, p_idx = 0;
+ 
+ 	/* 1st parameter should be ctid */
+ 	if (rowid)
+ 	{
+ 		p_values[p_idx] = rowid;
+ 		p_lengths[p_idx] = strlen(rowid) + 1;
+ 		p_idx++;
+ 	}
+ 
+ 	/* following parameters are as TupleDesc */
+ 	if (HeapTupleIsValid(tuple))
+ 	{
+ 		for (i = 0; i < tupdesc->natts; i++)
+ 		{
+ 			Form_pg_attribute	attr = tupdesc->attrs[i];
+ 			Datum		value;
+ 			bool		isnull;
+ 
+ 			if (attr->attisdropped)
+ 				continue;
+ 
+ 			value = heap_getattr(tuple, attr->attnum, tupdesc, &isnull);
+ 			if (isnull)
+ 			{
+ 				p_values[p_idx] = NULL;
+ 				p_lengths[p_idx] = 0;
+ 			}
+ 			else
+ 			{
+ 				p_values[p_idx] =
+ 					OutputFunctionCall(&fmstate->p_flinfo[p_idx], value);
+ 				p_lengths[p_idx] = strlen(p_values[p_idx]) + 1;
+ 			}
+ 			p_idx++;
+ 		}
+ 	}
+ 	return p_idx;
+ }
+ 
+ static int
+ postgresExecForeignInsert(ResultRelInfo *resultRelInfo,
+ 						  HeapTuple tuple)
+ {
+ 	PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state;
+ 	const char	  **p_values  = alloca(sizeof(char *) * fmstate->p_nums);
+ 	int			   *p_lengths = alloca(sizeof(int) * fmstate->p_nums);
+ 	AttrNumber		nattrs;
+ 	PGresult	   *res;
+ 	int				n_rows;
+ 
+ 	if (!fmstate->p_name)
+ 		prepare_foreign_modify(fmstate);
+ 
+ 	nattrs = setup_exec_prepared(resultRelInfo,
+ 								 NULL, tuple,
+ 								 p_values, p_lengths);
+ 	Assert(fmstate->p_nums == nattrs);
+ 
+ 	res = PQexecPrepared(fmstate->conn,
+ 						 fmstate->p_name,
+ 						 nattrs,
+ 						 p_values,
+ 						 p_lengths,
+ 						 NULL, 0);
+ 	if (!res ||  PQresultStatus(res) != PGRES_COMMAND_OK)
+ 	{
+ 		PQclear(res);
+ 		elog(ERROR, "could not execute prepared statement (%s): %s",
+ 			 fmstate->query, PQerrorMessage(fmstate->conn));
+     }
+ 	n_rows = atoi(PQcmdTuples(res));
+     PQclear(res);
+ 
+ 	return n_rows;
+ }
+ 
+ static int
+ postgresExecForeignDelete(ResultRelInfo *resultRelInfo, Datum rowid)
+ {
+ 	PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state;
+ 	const char	   *p_values[1];
+ 	int				p_lengths[1];
+ 	AttrNumber		nattrs;
+ 	PGresult	   *res;
+ 	int				n_rows;
+ 
+ 	if (!fmstate->p_name)
+ 		prepare_foreign_modify(fmstate);
+ 
+ 	nattrs = setup_exec_prepared(resultRelInfo,
+ 								 DatumGetCString(rowid), NULL,
+ 								 p_values, p_lengths);
+ 	Assert(fmstate->p_nums == nattrs);
+ 
+ 	res = PQexecPrepared(fmstate->conn,
+ 						 fmstate->p_name,
+ 						 nattrs,
+ 						 p_values,
+ 						 p_lengths,
+ 						 NULL, 0);
+ 	if (!res ||  PQresultStatus(res) != PGRES_COMMAND_OK)
+ 	{
+ 		PQclear(res);
+ 		elog(ERROR, "could not execute prepared statement (%s): %s",
+ 			 fmstate->query, PQerrorMessage(fmstate->conn));
+     }
+ 	n_rows = atoi(PQcmdTuples(res));
+     PQclear(res);
+ 
+ 	return n_rows;
+ }
+ 
+ static int
+ postgresExecForeignUpdate(ResultRelInfo *resultRelInfo,
+ 						  Datum rowid, HeapTuple tuple)
+ {
+ 	PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state;
+ 	const char	  **p_values  = alloca(sizeof(char *) * (fmstate->p_nums + 1));
+ 	int			   *p_lengths = alloca(sizeof(int) * (fmstate->p_nums + 1));
+ 	AttrNumber		nattrs;
+ 	PGresult	   *res;
+ 	int				n_rows;
+ 
+ 	if (!fmstate->p_name)
+ 		prepare_foreign_modify(fmstate);
+ 
+ 	nattrs = setup_exec_prepared(resultRelInfo,
+ 								 DatumGetCString(rowid), tuple,
+ 								 p_values, p_lengths);
+ 	Assert(fmstate->p_nums == nattrs);
+ 
+ 	res = PQexecPrepared(fmstate->conn,
+ 						 fmstate->p_name,
+ 						 nattrs,
+ 						 p_values,
+ 						 p_lengths,
+ 						 NULL, 0);
+ 	if (!res ||  PQresultStatus(res) != PGRES_COMMAND_OK)
+ 	{
+ 		PQclear(res);
+ 		elog(ERROR, "could not execute prepared statement (%s): %s",
+ 			 fmstate->query, PQerrorMessage(fmstate->conn));
+     }
+ 	n_rows = atoi(PQcmdTuples(res));
+     PQclear(res);
+ 
+ 	return n_rows;
+ }
+ 
+ static void
+ postgresEndForeignModify(ResultRelInfo *resultRelInfo)
+ {
+ 	PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state;
+ 
+ 	ReleaseConnection(fmstate->conn, false);
+ 	fmstate->conn = NULL;
+ }
diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h
new file mode 100644
index b5cefb8..3756a0d
*** a/contrib/postgres_fdw/postgres_fdw.h
--- b/contrib/postgres_fdw/postgres_fdw.h
*************** int GetFetchCountOption(ForeignTable *ta
*** 30,36 ****
  void deparseSimpleSql(StringInfo buf,
  					  PlannerInfo *root,
  					  RelOptInfo *baserel,
! 					  List *local_conds);
  void appendWhereClause(StringInfo buf,
  					   bool has_where,
  					   List *exprs,
--- 30,37 ----
  void deparseSimpleSql(StringInfo buf,
  					  PlannerInfo *root,
  					  RelOptInfo *baserel,
! 					  List *local_conds,
! 					  AttrNumber anum_rowid);
  void appendWhereClause(StringInfo buf,
  					   bool has_where,
  					   List *exprs,
*************** void classifyConditions(PlannerInfo *roo
*** 41,45 ****
--- 42,49 ----
  						List **param_conds,
  						List **local_conds);
  void deparseAnalyzeSql(StringInfo buf, Relation rel);
+ void deparseInsertSql(StringInfo buf, PlannerInfo *root, Index rtindex);
+ void deparseUpdateSql(StringInfo buf, PlannerInfo *root, Index rtindex);
+ void deparseDeleteSql(StringInfo buf, PlannerInfo *root, Index rtindex);
  
  #endif /* POSTGRESQL_FDW_H */
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
new file mode 100644
index 7845e70..70b09b4
*** a/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
*************** ERROR OUT;          -- ERROR
*** 300,305 ****
--- 300,318 ----
  SELECT srvname FROM postgres_fdw_connections;
  
  -- ===================================================================
+ -- test for writable foreign table stuff (PoC stage now)
+ -- ===================================================================
+ 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 *;
+ 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 *;
+ 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;
+ 
+ -- ===================================================================
  -- cleanup
  -- ===================================================================
  DROP OPERATOR === (int, int) CASCADE;
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
new file mode 100644
index d6e5d64..30f9c6b
*** a/doc/src/sgml/ddl.sgml
--- b/doc/src/sgml/ddl.sgml
*************** ANALYZE measurement;
*** 3066,3076 ****
      <firstterm>user mapping</>, which can provide additional options 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.
-    </para>
   </sect1>
  
   <sect1 id="ddl-others">
--- 3066,3071 ----
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
new file mode 100644
index 2d604ed..cbf3181
*** a/doc/src/sgml/fdwhandler.sgml
--- b/doc/src/sgml/fdwhandler.sgml
***************
*** 89,94 ****
--- 89,142 ----
  
      <para>
  <programlisting>
+ AttrNumber
+ GetForeignRelWidth(PlannerInfo *root,
+                    RelOptInfo *baserel,
+                    Relation foreignrel,
+                    bool inhparent,
+                    List *targetList);
+ </programlisting>
+      Obtain the width of the result set to be fetched during a foreign table scan.
+      This is an optional handler, and called before <literal>GetForeignRelSize</>
+      for a query involving a foreign table
+      (during the construction of <literal>RelOptInfo</>).
+      <literal>root</> is the planner's global information about the query,
+      <literal>baserel</> is the planner's information being constructed for
+      this query, and <literal>foreignrel</> is a <literal>Relation</>
+      descriptor of the foreign table.
+      <literal>inhparent</> is a boolean to show whether the relation is
+      an inheritance parent, even though foreign tables do not support table
+      inheritance right now. <literal>targetList</> is the list of
+      <literal>TargetEntry</> to be returned from the (sub-)query
+      that is currently in focus.
+     </para>
+ 
+     <para>
+      The result value of this function will be assigned to
+      <literal>baserel-&gt;max_attr</>, that means it is the expected number
+      of columns being fetched during the foreign table scan.
+      It should not be smaller than the number of regular columns in the definition
+      of this foreign table.  You can only return a number greater than his value to
+      acquire slots for some additional attributes, which are called
+      <firstterm>pseudo-columns</>.
+      A typical usage of a pseudo-column is to carry an identifier of
+      a particular remote row to be updated or deleted from the scanning stage
+      to the modifying stage when the foreign table is the target of
+      a data-modifying SQL statement.
+      You can return the result of the helper function
+      <literal>get_pseudo_rowid_column</> if this <literal>"rowid"</>
+      pseudo-column is the only one you need.
+    </para>
+ 
+    <para>
+      In addition to that, pseudo-columns can be used to off-load the burden of
+      complex calculations to foreign computing resources by replacing an
+      expression with a reference to its result, which is calculated on the
+      remote side rather than locally.
+    </para>
+ 
+     <para>
+ <programlisting>
  void
  GetForeignRelSize (PlannerInfo *root,
                     RelOptInfo *baserel,
*************** GetForeignRelSize (PlannerInfo *root,
*** 96,102 ****
  </programlisting>
  
       Obtain relation size estimates for a foreign table.  This is called
!      at the beginning of planning for a query involving 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
--- 144,151 ----
  </programlisting>
  
       Obtain relation size estimates for a foreign table.  This is called
!      for a query involving a foreign table at the beginning of planning
!      or right after <literal>GetForeignRelWidth</>, if that callback is configured.
       <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
*************** AcquireSampleRowsFunc (Relation relation
*** 315,320 ****
--- 364,479 ----
      </para>
  
      <para>
+      If a FDW supports writable foreign tables, it must implement at least both
+      <literal>BeginForeignModify</> and <literal>EndForeignModify</>.
+      In addition, some or all of <literal>ExecForeignInsert</>,
+      <literal>ExecForeignUpdate</> and <literal>ExecForeignDelete</> need
+      to be implement according to the capability of the FDW.
+     </para>     
+ 
+     <para>
+ <programlisting>
+ List *
+ PlanForeignModify(PlannerInfo *root,
+                   ModifyTable *plan,
+                   Index resultRelation,
+                   Plan *subplan);
+ </programlisting>
+      It allows FDW drivers to construct private information relevant to
+      the modification of the foreign table.  This private information must have
+      the form of a <literal>List *</>, which will be delivered as
+      third argument to <literal>BeginForeignModify</> during the execution stage.
+     </para>
+ 
+     <para>
+      <literal>root</> is the planner's global information about the query.
+      <literal>plan</> is the master plan to modify the result relation according
+      to its command type.  Please consider that <literal>ModifyTable</>
+      may have multiple result relations in a future revision, even though
+      currently there is no support for table inheritance on foreign tables.
+      <literal>resultRelation</> is an index for the result relation in the
+      range table entries, and <literal>subplan</> is the relevant scan plan;
+      that should be a <literal>ForeignScan</> for <literal>UPDATE</> or
+      <literal>DELETE</>, so the driver can access the private information of
+      the scan stage using this argument.
+     </para>
+ 
+     <para>
+ <programlisting>
+ void
+ BeginForeignModify (ModifyTableState *mtstate,
+                     ResultRelInfo *resultRelInfo,
+                     List *fdw_private,
+                     Plan *subplan,
+                     int eflags);
+ </programlisting>
+      It is invoked at beginning of foreign table modification, during
+      executor startup.  This routine should perform any initialization
+      needed prior to the actual table modifications, but not start
+      modifying the actual tuples. (That should be done during each call of
+      <function>ExecForeignInsert</>, <function>ExecForeignUpdate</> or
+      <function>ExecForeignDelete</>.)
+     </para>
+ 
+     <para>
+      The <structfield>ri_fdw_state</> field of <structname>ResultRelInfo</>
+      is reserved to store any private information relevant to this foreign table,
+      so it should be initialized to contain the per-scan state.
+      Please consider that <literal>ModifyTableState</> may have multiple
+      result relations in a future revision, even though currently there is no
+      support for table inheritance on foreign tables.  <literal>resultRelInfo</>
+      is the master information connected to this foreign table.
+      <literal>fdw_private</> is private information constructed in
+      <literal>PlanForeignModify</>, and <literal>subplan</> is the relevant
+      scan plan of this table modification.
+     </para>
+ 
+     <para>
+ <programlisting>
+ int
+ ExecForeignInsert (ResultRelInfo *resultRelInfo,
+                    HeapTuple tuple);
+ </programlisting>
+      Insert the given tuple into backing storage on behalf of the foreign table.
+      The supplied tuple is already formed according to the definition of
+      the relation, thus all the pseudo-columns are already filtered out.
+      It can return the number of rows being inserted, usually 1.
+     </para>
+ 
+     <para>
+ <programlisting>
+ int
+ ExecForeignDelete (ResultRelInfo *resultRelInfo,
+                    Datum rowid);
+ </programlisting>
+      Delete the tuple being identified with <literal>rowid</> from the backing
+      storage on behalf of the foreign table.
+      It can return the number of rows being deleted, usually 1.
+     </para>
+ 
+     <para>
+ <programlisting>
+ int
+ ExecForeignUpdate (ResultRelInfo *resultRelInfo,
+                    Datum rowid,
+                    HeapTuple tuple);
+ </programlisting>
+      Update the tuple being identified with <literal>rowid</> in the backing
+      storage on behalf of the foreign table with the given tuple.
+      It can return the number of rows being updated, usually 1.
+     </para>
+ 
+     <para>
+ <programlisting>
+ void
+ EndForeignModify (ResultRelInfo *resultRelInfo);
+ </programlisting>
+      End the modification 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>
       The <structname>FdwRoutine</> struct type is declared in
       <filename>src/include/foreign/fdwapi.h</>, which see for additional
       details.
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
new file mode 100644
index dbd3755..ccc1f3a
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 44,49 ****
--- 44,50 ----
  #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"
*************** void
*** 934,939 ****
--- 935,941 ----
  CheckValidResultRel(Relation resultRel, CmdType operation)
  {
  	TriggerDesc *trigDesc = resultRel->trigdesc;
+ 	FdwRoutine	*fdwroutine;
  
  	switch (resultRel->rd_rel->relkind)
  	{
*************** CheckValidResultRel(Relation resultRel, 
*** 985,994 ****
  			}
  			break;
  		case RELKIND_FOREIGN_TABLE:
! 			ereport(ERROR,
! 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! 					 errmsg("cannot change foreign table \"%s\"",
! 							RelationGetRelationName(resultRel))));
  			break;
  		default:
  			ereport(ERROR,
--- 987,1020 ----
  			}
  			break;
  		case RELKIND_FOREIGN_TABLE:
! 			fdwroutine = GetFdwRoutineByRelId(RelationGetRelid(resultRel));
! 			switch (operation)
! 			{
! 				case CMD_INSERT:
! 					if (!fdwroutine || !fdwroutine->ExecForeignInsert)
! 						ereport(ERROR,
! 							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
! 							 errmsg("cannot insert into foreign table \"%s\"",
! 									RelationGetRelationName(resultRel))));
! 					break;
! 				case CMD_UPDATE:
! 					if (!fdwroutine || !fdwroutine->ExecForeignUpdate)
! 						ereport(ERROR,
! 							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
! 							 errmsg("cannot update foreign table \"%s\"",
! 									RelationGetRelationName(resultRel))));
! 					break;
! 				case CMD_DELETE:
! 					if (!fdwroutine || !fdwroutine->ExecForeignDelete)
! 						ereport(ERROR,
! 							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
! 							 errmsg("cannot delete from foreign table \"%s\"",
! 									RelationGetRelationName(resultRel))));
! 					break;
! 				default:
! 					elog(ERROR, "unrecognized CmdType: %d", (int) operation);
! 					break;
! 			}
  			break;
  		default:
  			ereport(ERROR,
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
new file mode 100644
index 9204859..1ee626f
*** a/src/backend/executor/nodeForeignscan.c
--- b/src/backend/executor/nodeForeignscan.c
***************
*** 25,30 ****
--- 25,31 ----
  #include "executor/executor.h"
  #include "executor/nodeForeignscan.h"
  #include "foreign/fdwapi.h"
+ #include "nodes/nodeFuncs.h"
  #include "utils/rel.h"
  
  static TupleTableSlot *ForeignNext(ForeignScanState *node);
*************** ExecForeignScan(ForeignScanState *node)
*** 93,98 ****
--- 94,226 ----
  					(ExecScanRecheckMtd) ForeignRecheck);
  }
  
+ /*
+  * pseudo_column_walker
+  *
+  * helper routine of GetPseudoTupleDesc. It pulls Var nodes that reference
+  * pseudo columns from targetlis of the relation
+  */
+ typedef struct
+ {
+ 	Relation	relation;
+ 	Index		varno;
+ 	List	   *pcolumns;
+ 	AttrNumber	max_attno;
+ } pseudo_column_walker_context;
+ 
+ static bool
+ pseudo_column_walker(Node *node, pseudo_column_walker_context *context)
+ {
+ 	if (node == NULL)
+ 		return false;
+ 	if (IsA(node, Var))
+ 	{
+ 		Var		   *var = (Var *) node;
+ 		ListCell   *cell;
+ 
+ 		if (var->varno == context->varno && var->varlevelsup == 0 &&
+ 			var->varattno > RelationGetNumberOfAttributes(context->relation))
+ 		{
+ 			foreach (cell, context->pcolumns)
+ 			{
+ 				Var	   *temp = lfirst(cell);
+ 
+ 				if (temp->varattno == var->varattno)
+ 				{
+ 					if (!equal(var, temp))
+ 						elog(ERROR, "asymmetric pseudo column appeared");
+ 					break;
+ 				}
+ 			}
+ 			if (!cell)
+ 			{
+ 				context->pcolumns = lappend(context->pcolumns, var);
+ 				if (var->varattno > context->max_attno)
+ 					context->max_attno = var->varattno;
+ 			}
+ 		}
+ 		return false;
+ 	}
+ 
+ 	/* Should not find an unplanned subquery */
+ 	Assert(!IsA(node, Query));
+ 
+ 	return expression_tree_walker(node, pseudo_column_walker,
+ 								  (void *)context);
+ }
+ 
+ /*
+  * GetPseudoTupleDesc
+  *
+  * It generates TupleDesc structure including pseudo-columns if required.
+  */
+ static TupleDesc
+ GetPseudoTupleDesc(ForeignScan *node, Relation relation)
+ {
+ 	pseudo_column_walker_context context;
+ 	List	   *target_list = node->scan.plan.targetlist;
+ 	TupleDesc	tupdesc;
+ 	AttrNumber	attno;
+ 	ListCell   *cell;
+ 	ListCell   *prev;
+ 	bool		hasoid;
+ 
+ 	context.relation = relation;
+ 	context.varno = node->scan.scanrelid;
+ 	context.pcolumns = NIL;
+ 	context.max_attno = -1;
+ 
+ 	pseudo_column_walker((Node *)target_list, (void *)&context);
+ 	Assert(context.max_attno > RelationGetNumberOfAttributes(relation));
+ 
+ 	hasoid = RelationGetForm(relation)->relhasoids;
+ 	tupdesc = CreateTemplateTupleDesc(context.max_attno, hasoid);
+ 
+ 	for (attno = 1; attno <= context.max_attno; attno++)
+ 	{
+ 		/* case of regular columns */
+ 		if (attno <= RelationGetNumberOfAttributes(relation))
+ 		{
+ 			memcpy(tupdesc->attrs[attno - 1],
+ 				   RelationGetDescr(relation)->attrs[attno - 1],
+ 				   ATTRIBUTE_FIXED_PART_SIZE);
+ 			continue;
+ 		}
+ 
+ 		/* case of pseudo columns */
+ 		prev = NULL;
+ 		foreach (cell, context.pcolumns)
+ 		{
+ 			Var	   *var = lfirst(cell);
+ 
+ 			if (var->varattno == attno)
+ 			{
+ 				char		namebuf[NAMEDATALEN];
+ 
+ 				snprintf(namebuf, sizeof(namebuf),
+ 						 "pseudo_column_%d", attno);
+ 
+ 				TupleDescInitEntry(tupdesc,
+ 								   attno,
+ 								   namebuf,
+ 								   var->vartype,
+ 								   var->vartypmod,
+ 								   0);
+ 				TupleDescInitEntryCollation(tupdesc,
+ 											attno,
+ 											var->varcollid);
+ 				context.pcolumns
+ 					= list_delete_cell(context.pcolumns, cell, prev);
+ 				break;
+ 			}
+ 			prev = cell;
+ 		}
+ 		if (!cell)
+ 			elog(ERROR, "pseudo column %d of %s not in target list",
+ 				 attno, RelationGetRelationName(relation));
+ 	}
+ 	return tupdesc;
+ }
  
  /* ----------------------------------------------------------------
   *		ExecInitForeignScan
*************** ExecInitForeignScan(ForeignScan *node, E
*** 103,108 ****
--- 231,237 ----
  {
  	ForeignScanState *scanstate;
  	Relation	currentRelation;
+ 	TupleDesc	tupdesc;
  	FdwRoutine *fdwroutine;
  
  	/* check for unsupported flags */
*************** ExecInitForeignScan(ForeignScan *node, E
*** 149,155 ****
  	/*
  	 * get the scan type from the relation descriptor.
  	 */
! 	ExecAssignScanType(&scanstate->ss, RelationGetDescr(currentRelation));
  
  	/*
  	 * Initialize result tuple type and projection info.
--- 278,289 ----
  	/*
  	 * get the scan type from the relation descriptor.
  	 */
! 	if (node->fsPseudoCol)
! 		tupdesc = GetPseudoTupleDesc(node, currentRelation);
! 	else
! 		tupdesc = RelationGetDescr(currentRelation);
! 
! 	ExecAssignScanType(&scanstate->ss, tupdesc);
  
  	/*
  	 * Initialize result tuple type and projection info.
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
new file mode 100644
index d31015c..bf9421e
*** a/src/backend/executor/nodeModifyTable.c
--- b/src/backend/executor/nodeModifyTable.c
***************
*** 42,47 ****
--- 42,48 ----
  #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"
*************** ExecInsert(TupleTableSlot *slot,
*** 170,175 ****
--- 171,177 ----
  	Relation	resultRelationDesc;
  	Oid			newId;
  	List	   *recheckIndexes = NIL;
+ 	int			num_rows = 1;
  
  	/*
  	 * get the heap tuple out of the tuple table slot, making sure we have a
*************** ExecInsert(TupleTableSlot *slot,
*** 225,230 ****
--- 227,240 ----
  
  		newId = InvalidOid;
  	}
+ 	else if (resultRelInfo->ri_fdwroutine)
+ 	{
+ 		FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+ 
+ 		num_rows = fdwroutine->ExecForeignInsert(resultRelInfo, tuple);
+ 
+ 		newId = InvalidOid;
+ 	}
  	else
  	{
  		/*
*************** ExecInsert(TupleTableSlot *slot,
*** 252,258 ****
  
  	if (canSetTag)
  	{
! 		(estate->es_processed)++;
  		estate->es_lastoid = newId;
  		setLastTid(&(tuple->t_self));
  	}
--- 262,268 ----
  
  	if (canSetTag)
  	{
! 		(estate->es_processed) += num_rows;
  		estate->es_lastoid = newId;
  		setLastTid(&(tuple->t_self));
  	}
*************** ExecInsert(TupleTableSlot *slot,
*** 285,291 ****
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecDelete(ItemPointer tupleid,
  		   HeapTupleHeader oldtuple,
  		   TupleTableSlot *planSlot,
  		   EPQState *epqstate,
--- 295,301 ----
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecDelete(Datum rowid,
  		   HeapTupleHeader oldtuple,
  		   TupleTableSlot *planSlot,
  		   EPQState *epqstate,
*************** ExecDelete(ItemPointer tupleid,
*** 296,301 ****
--- 306,312 ----
  	Relation	resultRelationDesc;
  	HTSU_Result result;
  	HeapUpdateFailureData hufd;
+ 	int			num_rows = 1;
  
  	/*
  	 * get information on the (current) result relation
*************** ExecDelete(ItemPointer tupleid,
*** 310,316 ****
  		bool		dodelete;
  
  		dodelete = ExecBRDeleteTriggers(estate, epqstate, resultRelInfo,
! 										tupleid);
  
  		if (!dodelete)			/* "do nothing" */
  			return NULL;
--- 321,327 ----
  		bool		dodelete;
  
  		dodelete = ExecBRDeleteTriggers(estate, epqstate, resultRelInfo,
! 										(ItemPointer)DatumGetPointer(rowid));
  
  		if (!dodelete)			/* "do nothing" */
  			return NULL;
*************** ExecDelete(ItemPointer tupleid,
*** 334,341 ****
--- 345,360 ----
  		if (!dodelete)			/* "do nothing" */
  			return NULL;
  	}
+ 	else if (resultRelInfo->ri_fdwroutine)
+ 	{
+ 		FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+ 
+ 		num_rows = fdwroutine->ExecForeignDelete(resultRelInfo, rowid);
+ 	}
  	else
  	{
+ 		ItemPointer	tupleid = (ItemPointer) DatumGetPointer(rowid);
+ 
  		/*
  		 * delete the tuple
  		 *
*************** ldelete:;
*** 430,439 ****
  	}
  
  	if (canSetTag)
! 		(estate->es_processed)++;
  
  	/* AFTER ROW DELETE Triggers */
! 	ExecARDeleteTriggers(estate, resultRelInfo, tupleid);
  
  	/* Process RETURNING if present */
  	if (resultRelInfo->ri_projectReturning)
--- 449,459 ----
  	}
  
  	if (canSetTag)
! 		(estate->es_processed) += num_rows;
  
  	/* AFTER ROW DELETE Triggers */
! 	ExecARDeleteTriggers(estate, resultRelInfo,
! 						 (ItemPointer)DatumGetPointer(rowid));
  
  	/* Process RETURNING if present */
  	if (resultRelInfo->ri_projectReturning)
*************** ldelete:;
*** 457,463 ****
  		}
  		else
  		{
! 			deltuple.t_self = *tupleid;
  			if (!heap_fetch(resultRelationDesc, SnapshotAny,
  							&deltuple, &delbuffer, false, NULL))
  				elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
--- 477,484 ----
  		}
  		else
  		{
! 			ItemPointerCopy((ItemPointer)DatumGetPointer(rowid),
! 							&deltuple.t_self);
  			if (!heap_fetch(resultRelationDesc, SnapshotAny,
  							&deltuple, &delbuffer, false, NULL))
  				elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
*************** ldelete:;
*** 499,505 ****
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecUpdate(ItemPointer tupleid,
  		   HeapTupleHeader oldtuple,
  		   TupleTableSlot *slot,
  		   TupleTableSlot *planSlot,
--- 520,526 ----
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
! ExecUpdate(Datum rowid,
  		   HeapTupleHeader oldtuple,
  		   TupleTableSlot *slot,
  		   TupleTableSlot *planSlot,
*************** ExecUpdate(ItemPointer tupleid,
*** 513,518 ****
--- 534,540 ----
  	HTSU_Result result;
  	HeapUpdateFailureData hufd;
  	List	   *recheckIndexes = NIL;
+ 	int			num_rows = 1;
  
  	/*
  	 * abort the operation if not running transactions
*************** ExecUpdate(ItemPointer tupleid,
*** 537,543 ****
  		resultRelInfo->ri_TrigDesc->trig_update_before_row)
  	{
  		slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
! 									tupleid, slot);
  
  		if (slot == NULL)		/* "do nothing" */
  			return NULL;
--- 559,565 ----
  		resultRelInfo->ri_TrigDesc->trig_update_before_row)
  	{
  		slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
! 									(ItemPointer)DatumGetPointer(rowid), slot);
  
  		if (slot == NULL)		/* "do nothing" */
  			return NULL;
*************** ExecUpdate(ItemPointer tupleid,
*** 567,574 ****
--- 589,604 ----
  		/* trigger might have changed tuple */
  		tuple = ExecMaterializeSlot(slot);
  	}
+ 	else if (resultRelInfo->ri_fdwroutine)
+ 	{
+ 		FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+ 
+ 		num_rows = fdwroutine->ExecForeignUpdate(resultRelInfo, rowid, tuple);
+ 	}
  	else
  	{
+ 		ItemPointer	tupleid = (ItemPointer) DatumGetPointer(rowid);
+ 
  		/*
  		 * Check the constraints of the tuple
  		 *
*************** lreplace:;
*** 687,697 ****
  	}
  
  	if (canSetTag)
! 		(estate->es_processed)++;
  
  	/* AFTER ROW UPDATE Triggers */
! 	ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple,
! 						 recheckIndexes);
  
  	list_free(recheckIndexes);
  
--- 717,728 ----
  	}
  
  	if (canSetTag)
! 		(estate->es_processed) += num_rows;
  
  	/* AFTER ROW UPDATE Triggers */
! 	ExecARUpdateTriggers(estate, resultRelInfo,
! 						 (ItemPointer) DatumGetPointer(rowid),
! 						 tuple, recheckIndexes);
  
  	list_free(recheckIndexes);
  
*************** ExecModifyTable(ModifyTableState *node)
*** 771,776 ****
--- 802,808 ----
  	TupleTableSlot *planSlot;
  	ItemPointer tupleid = NULL;
  	ItemPointerData tuple_ctid;
+ 	Datum		rowid = 0;
  	HeapTupleHeader oldtuple = NULL;
  
  	/*
*************** ExecModifyTable(ModifyTableState *node)
*** 859,875 ****
  		if (junkfilter != NULL)
  		{
  			/*
! 			 * extract the 'ctid' or 'wholerow' junk attribute.
  			 */
  			if (operation == CMD_UPDATE || operation == CMD_DELETE)
  			{
  				Datum		datum;
  				bool		isNull;
  
! 				if (resultRelInfo->ri_RelationDesc->rd_rel->relkind == RELKIND_RELATION)
  				{
  					datum = ExecGetJunkAttribute(slot,
! 												 junkfilter->jf_junkAttNo,
  												 &isNull);
  					/* shouldn't ever get a null result... */
  					if (isNull)
--- 891,909 ----
  		if (junkfilter != NULL)
  		{
  			/*
! 			 * extract the 'ctid', 'rowid' or 'wholerow' junk attribute.
  			 */
  			if (operation == CMD_UPDATE || operation == CMD_DELETE)
  			{
+ 				char		relkind;
  				Datum		datum;
  				bool		isNull;
  
! 				relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
! 				if (relkind == RELKIND_RELATION)
  				{
  					datum = ExecGetJunkAttribute(slot,
! 												 junkfilter->jf_junkRowidNo,
  												 &isNull);
  					/* shouldn't ever get a null result... */
  					if (isNull)
*************** ExecModifyTable(ModifyTableState *node)
*** 877,889 ****
  
  					tupleid = (ItemPointer) DatumGetPointer(datum);
  					tuple_ctid = *tupleid;		/* be sure we don't free
! 												 * ctid!! */
! 					tupleid = &tuple_ctid;
  				}
  				else
  				{
  					datum = ExecGetJunkAttribute(slot,
! 												 junkfilter->jf_junkAttNo,
  												 &isNull);
  					/* shouldn't ever get a null result... */
  					if (isNull)
--- 911,943 ----
  
  					tupleid = (ItemPointer) DatumGetPointer(datum);
  					tuple_ctid = *tupleid;		/* be sure we don't free
! 												 * ctid ! */
! 					rowid = PointerGetDatum(&tuple_ctid);
! 				}
! 				else if (relkind == RELKIND_FOREIGN_TABLE)
! 				{
! 					datum = ExecGetJunkAttribute(slot,
! 												 junkfilter->jf_junkRowidNo,
! 												 &isNull);
! 					/* shouldn't ever get a null result... */
! 					if (isNull)
! 						elog(ERROR, "rowid is NULL");
! 
! 					rowid = datum;
! 
! 					datum = ExecGetJunkAttribute(slot,
! 												 junkfilter->jf_junkRecordNo,
! 												 &isNull);
! 					/* shouldn't ever get a null result... */
! 					if (isNull)
! 						elog(ERROR, "wholerow is NULL");
! 
! 					oldtuple = DatumGetHeapTupleHeader(datum);
  				}
  				else
  				{
  					datum = ExecGetJunkAttribute(slot,
! 												 junkfilter->jf_junkRecordNo,
  												 &isNull);
  					/* shouldn't ever get a null result... */
  					if (isNull)
*************** ExecModifyTable(ModifyTableState *node)
*** 906,916 ****
  				slot = ExecInsert(slot, planSlot, estate, node->canSetTag);
  				break;
  			case CMD_UPDATE:
! 				slot = ExecUpdate(tupleid, oldtuple, slot, planSlot,
  								&node->mt_epqstate, estate, node->canSetTag);
  				break;
  			case CMD_DELETE:
! 				slot = ExecDelete(tupleid, oldtuple, planSlot,
  								&node->mt_epqstate, estate, node->canSetTag);
  				break;
  			default:
--- 960,970 ----
  				slot = ExecInsert(slot, planSlot, estate, node->canSetTag);
  				break;
  			case CMD_UPDATE:
! 				slot = ExecUpdate(rowid, oldtuple, slot, planSlot,
  								&node->mt_epqstate, estate, node->canSetTag);
  				break;
  			case CMD_DELETE:
! 				slot = ExecDelete(rowid, oldtuple, planSlot,
  								&node->mt_epqstate, estate, node->canSetTag);
  				break;
  			default:
*************** ExecInitModifyTable(ModifyTable *node, E
*** 997,1002 ****
--- 1051,1058 ----
  	i = 0;
  	foreach(l, node->plans)
  	{
+ 		char	relkind;
+ 
  		subplan = (Plan *) lfirst(l);
  
  		/*
*************** ExecInitModifyTable(ModifyTable *node, E
*** 1022,1027 ****
--- 1078,1101 ----
  		estate->es_result_relation_info = resultRelInfo;
  		mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
  
+ 		/*
+ 		 * Also tells FDW extensions to init the plan for this result rel
+ 		 */
+ 		relkind = RelationGetForm(resultRelInfo->ri_RelationDesc)->relkind;
+ 		if (relkind == RELKIND_FOREIGN_TABLE)
+ 		{
+ 			Oid		relid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+ 			FdwRoutine *fdwroutine = GetFdwRoutineByRelId(relid);
+ 			List   *fdwprivate = list_nth(node->fdwPrivList, i);
+ 
+ 			Assert(fdwroutine != NULL);
+ 			resultRelInfo->ri_fdwroutine = fdwroutine;
+ 			resultRelInfo->ri_fdw_state = NULL;
+ 
+ 			if (fdwroutine->BeginForeignModify)
+ 				fdwroutine->BeginForeignModify(mtstate, resultRelInfo,
+ 											   fdwprivate, subplan, eflags);
+ 		}
  		resultRelInfo++;
  		i++;
  	}
*************** ExecInitModifyTable(ModifyTable *node, E
*** 1163,1168 ****
--- 1237,1244 ----
  			for (i = 0; i < nplans; i++)
  			{
  				JunkFilter *j;
+ 				char		relkind =
+ 				    RelationGetForm(resultRelInfo->ri_RelationDesc)->relkind;
  
  				subplan = mtstate->mt_plans[i]->plan;
  				if (operation == CMD_INSERT || operation == CMD_UPDATE)
*************** ExecInitModifyTable(ModifyTable *node, E
*** 1176,1191 ****
  				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)
  					{
! 						j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
! 						if (!AttributeNumberIsValid(j->jf_junkAttNo))
  							elog(ERROR, "could not find junk ctid column");
  					}
  					else
  					{
! 						j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
! 						if (!AttributeNumberIsValid(j->jf_junkAttNo))
  							elog(ERROR, "could not find junk wholerow column");
  					}
  				}
--- 1252,1278 ----
  				if (operation == CMD_UPDATE || operation == CMD_DELETE)
  				{
  					/* For UPDATE/DELETE, find the appropriate junk attr now */
! 					if (relkind == RELKIND_RELATION)
  					{
! 						j->jf_junkRowidNo = ExecFindJunkAttribute(j, "ctid");
! 						if (!AttributeNumberIsValid(j->jf_junkRowidNo))
  							elog(ERROR, "could not find junk ctid column");
  					}
+ 					else if (relkind == RELKIND_FOREIGN_TABLE)
+ 					{
+ 						j->jf_junkRowidNo = ExecFindJunkAttribute(j, "rowid");
+ 						if (!AttributeNumberIsValid(j->jf_junkRowidNo))
+ 							elog(ERROR, "could not find junk rowid column");
+ 						j->jf_junkRecordNo
+ 							= ExecFindJunkAttribute(j, "record");
+ 						if (!AttributeNumberIsValid(j->jf_junkRecordNo))
+ 							elog(ERROR, "could not find junk wholerow column");
+ 					}
  					else
  					{
! 						j->jf_junkRecordNo
! 							= ExecFindJunkAttribute(j, "wholerow");
! 						if (!AttributeNumberIsValid(j->jf_junkRecordNo))
  							elog(ERROR, "could not find junk wholerow column");
  					}
  				}
*************** ExecEndModifyTable(ModifyTableState *nod
*** 1239,1244 ****
--- 1326,1346 ----
  {
  	int			i;
  
+ 	/* Let the FDW shut dowm */
+ 	for (i=0; i < node->ps.state->es_num_result_relations; i++)
+ 	{
+ 		ResultRelInfo  *resultRelInfo
+ 			= &node->ps.state->es_result_relations[i];
+ 
+ 		if (resultRelInfo->ri_fdwroutine)
+ 		{
+ 			FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+ 
+ 			if (fdwroutine->EndForeignModify)
+ 				fdwroutine->EndForeignModify(resultRelInfo);
+ 		}
+ 	}
+ 
  	/*
  	 * Free the exprcontext
  	 */
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
new file mode 100644
index d8845aa..4363a28
*** a/src/backend/foreign/foreign.c
--- b/src/backend/foreign/foreign.c
*************** get_foreign_server_oid(const char *serve
*** 571,573 ****
--- 571,641 ----
  				 errmsg("server \"%s\" does not exist", servername)));
  	return oid;
  }
+ 
+ /*
+  * get_pseudo_rowid_column
+  *
+  * It picks up an attribute number to be used for the pseudo rowid column,
+  * if it exists.  It should be injected at rewriteHandler.c if the supplied query
+  * is a UPDATE or DELETE command.  Elsewhere, it returns InvalidAttrNumber.
+  */
+ AttrNumber
+ get_pseudo_rowid_column(RelOptInfo *baserel, List *targetList)
+ {
+ 	ListCell   *cell;
+ 
+ 	foreach (cell, targetList)
+ 	{
+ 		TargetEntry *tle = lfirst(cell);
+ 
+ 		if (tle->resjunk &&
+ 			tle->resname && strcmp(tle->resname, "rowid") == 0)
+ 		{
+ 			Var	   *var;
+ 
+ 			if (!IsA(tle->expr, Var))
+ 				elog(ERROR, "unexpected node on junk rowid entry: %d",
+ 					 (int) nodeTag(tle->expr));
+ 
+ 			var = (Var *) tle->expr;
+ 			if (baserel->relid == var->varno)
+ 				return var->varattno;
+ 		}
+ 	}
+ 	return InvalidAttrNumber;
+ }
+ 
+ /*
+  * lookup_foreign_scan_plan
+  *
+  * It looks up the ForeignScan plan node with the supplied range-table id.
+  * Its typical usage is for PlanForeignModify to get the underlying scan plan on
+  * UPDATE or DELETE commands.
+  */
+ ForeignScan *
+ lookup_foreign_scan_plan(Plan *subplan, Index rtindex)
+ {
+ 	if (!subplan)
+ 		return NULL;
+ 
+ 	else if (IsA(subplan, ForeignScan))
+ 	{
+ 		ForeignScan	   *fscan = (ForeignScan *) subplan;
+ 
+ 		if (fscan->scan.scanrelid == rtindex)
+ 			return fscan;
+ 	}
+ 	else
+ 	{
+ 		ForeignScan    *fscan;
+ 
+ 		fscan = lookup_foreign_scan_plan(subplan->lefttree, rtindex);
+ 		if (fscan != NULL)
+ 			return fscan;
+ 
+ 		fscan = lookup_foreign_scan_plan(subplan->righttree, rtindex);
+ 		if (fscan != NULL)
+ 			return fscan;
+ 	}
+ 	return NULL;
+ }
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
new file mode 100644
index 9387ee9..5b1e8ac
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
*************** _copyModifyTable(const ModifyTable *from
*** 181,186 ****
--- 181,187 ----
  	COPY_NODE_FIELD(returningLists);
  	COPY_NODE_FIELD(rowMarks);
  	COPY_SCALAR_FIELD(epqParam);
+ 	COPY_NODE_FIELD(fdwPrivList);
  
  	return newnode;
  }
*************** _copyForeignScan(const ForeignScan *from
*** 594,599 ****
--- 595,601 ----
  	COPY_NODE_FIELD(fdw_exprs);
  	COPY_NODE_FIELD(fdw_private);
  	COPY_SCALAR_FIELD(fsSystemCol);
+ 	COPY_SCALAR_FIELD(fsPseudoCol);
  
  	return newnode;
  }
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
new file mode 100644
index 35c6287..c32ebd3
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
*************** _outModifyTable(StringInfo str, const Mo
*** 335,340 ****
--- 335,341 ----
  	WRITE_NODE_FIELD(returningLists);
  	WRITE_NODE_FIELD(rowMarks);
  	WRITE_INT_FIELD(epqParam);
+ 	WRITE_NODE_FIELD(fdwPrivList);
  }
  
  static void
*************** _outForeignScan(StringInfo str, const Fo
*** 562,567 ****
--- 563,569 ----
  	WRITE_NODE_FIELD(fdw_exprs);
  	WRITE_NODE_FIELD(fdw_private);
  	WRITE_BOOL_FIELD(fsSystemCol);
+ 	WRITE_BOOL_FIELD(fsPseudoCol);
  }
  
  static void
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
new file mode 100644
index 030f420..91db1b8
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
***************
*** 39,44 ****
--- 39,45 ----
  #include "parser/parse_clause.h"
  #include "parser/parsetree.h"
  #include "utils/lsyscache.h"
+ #include "utils/rel.h"
  
  
  static Plan *create_plan_recurse(PlannerInfo *root, Path *best_path);
*************** create_foreignscan_plan(PlannerInfo *roo
*** 1943,1948 ****
--- 1944,1951 ----
  	RelOptInfo *rel = best_path->path.parent;
  	Index		scan_relid = rel->relid;
  	RangeTblEntry *rte;
+ 	Relation	relation;
+ 	AttrNumber	num_attrs;
  	int			i;
  
  	/* it should be a base rel... */
*************** create_foreignscan_plan(PlannerInfo *roo
*** 2001,2006 ****
--- 2004,2025 ----
  		}
  	}
  
+ 	/*
+ 	 * Also, detect whether any pseudo columns are requested from rel.
+ 	 */
+ 	relation = heap_open(rte->relid, NoLock);
+ 	scan_plan->fsPseudoCol = false;
+ 	num_attrs = RelationGetNumberOfAttributes(relation);
+ 	for (i = num_attrs + 1; i <= rel->max_attr; i++)
+ 	{
+ 		if (!bms_is_empty(rel->attr_needed[i - rel->min_attr]))
+ 		{
+ 			scan_plan->fsPseudoCol = true;
+ 			break;
+ 		}
+ 	}
+ 	heap_close(relation, NoLock);
+ 
  	return scan_plan;
  }
  
*************** make_result(PlannerInfo *root,
*** 4695,4701 ****
   * to make it look better sometime.
   */
  ModifyTable *
! make_modifytable(CmdType operation, bool canSetTag,
  				 List *resultRelations,
  				 List *subplans, List *returningLists,
  				 List *rowMarks, int epqParam)
--- 4714,4721 ----
   * to make it look better sometime.
   */
  ModifyTable *
! make_modifytable(PlannerInfo *root,
! 				 CmdType operation, bool canSetTag,
  				 List *resultRelations,
  				 List *subplans, List *returningLists,
  				 List *rowMarks, int epqParam)
*************** make_modifytable(CmdType operation, bool
*** 4704,4709 ****
--- 4724,4731 ----
  	Plan	   *plan = &node->plan;
  	double		total_size;
  	ListCell   *subnode;
+ 	ListCell   *resultRel;
+ 	List	   *fdw_priv_list = NIL;
  
  	Assert(list_length(resultRelations) == list_length(subplans));
  	Assert(returningLists == NIL ||
*************** make_modifytable(CmdType operation, bool
*** 4746,4751 ****
--- 4768,4797 ----
  	node->rowMarks = rowMarks;
  	node->epqParam = epqParam;
  
+ 	/*
+ 	 * Allow FDW driver to construct its private plan if the result relation
+ 	 * is a foreign table.
+ 	 */
+ 	forboth (resultRel, resultRelations, subnode, subplans)
+ 	{
+ 		RangeTblEntry  *rte = rt_fetch(lfirst_int(resultRel),
+ 									   root->parse->rtable);
+ 		List		   *fdw_private = NIL;
+ 		char			relkind = get_rel_relkind(rte->relid);
+ 
+ 		if (relkind == RELKIND_FOREIGN_TABLE)
+ 		{
+ 			FdwRoutine *fdwroutine = GetFdwRoutineByRelId(rte->relid);
+ 
+ 			if (fdwroutine && fdwroutine->PlanForeignModify)
+ 				fdw_private = fdwroutine->PlanForeignModify(root, node,
+ 													lfirst_int(resultRel),
+ 													(Plan *) lfirst(subnode));
+ 		}
+ 		fdw_priv_list = lappend(fdw_priv_list, fdw_private);
+ 	}
+ 	node->fdwPrivList = fdw_priv_list;
+ 
  	return node;
  }
  
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
new file mode 100644
index bd719b5..57ba192
*** a/src/backend/optimizer/plan/initsplan.c
--- b/src/backend/optimizer/plan/initsplan.c
*************** static void check_hashjoinable(RestrictI
*** 85,91 ****
   * "other rel" RelOptInfos for the members of any appendrels we find here.)
   */
  void
! add_base_rels_to_query(PlannerInfo *root, Node *jtnode)
  {
  	if (jtnode == NULL)
  		return;
--- 85,91 ----
   * "other rel" RelOptInfos for the members of any appendrels we find here.)
   */
  void
! add_base_rels_to_query(PlannerInfo *root, List *tlist, Node *jtnode)
  {
  	if (jtnode == NULL)
  		return;
*************** add_base_rels_to_query(PlannerInfo *root
*** 93,99 ****
  	{
  		int			varno = ((RangeTblRef *) jtnode)->rtindex;
  
! 		(void) build_simple_rel(root, varno, RELOPT_BASEREL);
  	}
  	else if (IsA(jtnode, FromExpr))
  	{
--- 93,99 ----
  	{
  		int			varno = ((RangeTblRef *) jtnode)->rtindex;
  
! 		(void) build_simple_rel(root, varno, tlist, RELOPT_BASEREL);
  	}
  	else if (IsA(jtnode, FromExpr))
  	{
*************** add_base_rels_to_query(PlannerInfo *root
*** 101,114 ****
  		ListCell   *l;
  
  		foreach(l, f->fromlist)
! 			add_base_rels_to_query(root, lfirst(l));
  	}
  	else if (IsA(jtnode, JoinExpr))
  	{
  		JoinExpr   *j = (JoinExpr *) jtnode;
  
! 		add_base_rels_to_query(root, j->larg);
! 		add_base_rels_to_query(root, j->rarg);
  	}
  	else
  		elog(ERROR, "unrecognized node type: %d",
--- 101,114 ----
  		ListCell   *l;
  
  		foreach(l, f->fromlist)
! 			add_base_rels_to_query(root, tlist, lfirst(l));
  	}
  	else if (IsA(jtnode, JoinExpr))
  	{
  		JoinExpr   *j = (JoinExpr *) jtnode;
  
! 		add_base_rels_to_query(root, tlist, j->larg);
! 		add_base_rels_to_query(root, tlist, j->rarg);
  	}
  	else
  		elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
new file mode 100644
index c2488a4..4442444
*** a/src/backend/optimizer/plan/planmain.c
--- b/src/backend/optimizer/plan/planmain.c
*************** query_planner(PlannerInfo *root, List *t
*** 112,117 ****
--- 112,120 ----
  	 */
  	if (parse->jointree->fromlist == NIL)
  	{
+ 		/* Make a flattened version of the rangetable for faster access */
+ 		setup_simple_rel_arrays(root);
+ 
  		/* We need a trivial path result */
  		*cheapest_path = (Path *)
  			create_result_path((List *) parse->jointree->quals);
*************** query_planner(PlannerInfo *root, List *t
*** 163,169 ****
  	 * rangetable may contain RTEs for rels not actively part of the query,
  	 * for example views.  We don't want to make RelOptInfos for them.
  	 */
! 	add_base_rels_to_query(root, (Node *) parse->jointree);
  
  	/*
  	 * Examine the targetlist and join tree, adding entries to baserel
--- 166,172 ----
  	 * rangetable may contain RTEs for rels not actively part of the query,
  	 * for example views.  We don't want to make RelOptInfos for them.
  	 */
! 	add_base_rels_to_query(root, tlist, (Node *) parse->jointree);
  
  	/*
  	 * Examine the targetlist and join tree, adding entries to baserel
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
new file mode 100644
index b61005f..2d72244
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
*************** subquery_planner(PlannerGlobal *glob, Qu
*** 571,577 ****
  			else
  				rowMarks = root->rowMarks;
  
! 			plan = (Plan *) make_modifytable(parse->commandType,
  											 parse->canSetTag,
  									   list_make1_int(parse->resultRelation),
  											 list_make1(plan),
--- 571,578 ----
  			else
  				rowMarks = root->rowMarks;
  
! 			plan = (Plan *) make_modifytable(root,
! 											 parse->commandType,
  											 parse->canSetTag,
  									   list_make1_int(parse->resultRelation),
  											 list_make1(plan),
*************** inheritance_planner(PlannerInfo *root)
*** 964,970 ****
  		rowMarks = root->rowMarks;
  
  	/* And last, tack on a ModifyTable node to do the UPDATE/DELETE work */
! 	return (Plan *) make_modifytable(parse->commandType,
  									 parse->canSetTag,
  									 resultRelations,
  									 subplans,
--- 965,972 ----
  		rowMarks = root->rowMarks;
  
  	/* And last, tack on a ModifyTable node to do the UPDATE/DELETE work */
! 	return (Plan *) make_modifytable(root,
! 									 parse->commandType,
  									 parse->canSetTag,
  									 resultRelations,
  									 subplans,
*************** plan_cluster_use_sort(Oid tableOid, Oid 
*** 3385,3391 ****
  	setup_simple_rel_arrays(root);
  
  	/* Build RelOptInfo */
! 	rel = build_simple_rel(root, 1, RELOPT_BASEREL);
  
  	/* Locate IndexOptInfo for the target index */
  	indexInfo = NULL;
--- 3387,3393 ----
  	setup_simple_rel_arrays(root);
  
  	/* Build RelOptInfo */
! 	rel = build_simple_rel(root, 1, NIL, RELOPT_BASEREL);
  
  	/* Locate IndexOptInfo for the target index */
  	indexInfo = NULL;
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
new file mode 100644
index b91e9f4..61bc447
*** a/src/backend/optimizer/prep/prepunion.c
--- b/src/backend/optimizer/prep/prepunion.c
*************** recurse_set_operations(Node *setOp, Plan
*** 236,242 ****
  		 * used for anything here, but it carries the subroot data structures
  		 * forward to setrefs.c processing.
  		 */
! 		rel = build_simple_rel(root, rtr->rtindex, RELOPT_BASEREL);
  
  		/* plan_params should not be in use in current query level */
  		Assert(root->plan_params == NIL);
--- 236,243 ----
  		 * used for anything here, but it carries the subroot data structures
  		 * forward to setrefs.c processing.
  		 */
! 		rel = build_simple_rel(root, rtr->rtindex, refnames_tlist,
! 							   RELOPT_BASEREL);
  
  		/* plan_params should not be in use in current query level */
  		Assert(root->plan_params == NIL);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
new file mode 100644
index 04d5028..7d6bc4b
*** a/src/backend/optimizer/util/plancat.c
--- b/src/backend/optimizer/util/plancat.c
***************
*** 25,30 ****
--- 25,31 ----
  #include "access/xlog.h"
  #include "catalog/catalog.h"
  #include "catalog/heap.h"
+ #include "foreign/fdwapi.h"
  #include "miscadmin.h"
  #include "nodes/makefuncs.h"
  #include "optimizer/clauses.h"
*************** static List *build_index_tlist(PlannerIn
*** 80,86 ****
   */
  void
  get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
! 				  RelOptInfo *rel)
  {
  	Index		varno = rel->relid;
  	Relation	relation;
--- 81,87 ----
   */
  void
  get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
! 				  List *tlist, RelOptInfo *rel)
  {
  	Index		varno = rel->relid;
  	Relation	relation;
*************** get_relation_info(PlannerInfo *root, Oid
*** 104,109 ****
--- 105,134 ----
  	rel->max_attr = RelationGetNumberOfAttributes(relation);
  	rel->reltablespace = RelationGetForm(relation)->reltablespace;
  
+ 	/*
+ 	 * Adjust width of attr_needed slot in case FDW extension wants
+ 	 * to return pseudo-columns in addition to the columns in its
+ 	 * table definition.
+ 	 * GetForeignRelWidth, an optional FDW handler, enables a FDW
+ 	 * to save properties of a pseudo-column in its private field.
+ 	 * When the foreign table is the target of UPDATE/DELETE, the query rewriter
+ 	 * injects a "rowid" pseudo-column to track the remote row to be modified,
+ 	 * so the FDW has to track which varattno shall perform as "rowid".
+ 	 */
+ 	if (RelationGetForm(relation)->relkind == RELKIND_FOREIGN_TABLE)
+ 	{
+ 		FdwRoutine *fdwroutine = GetFdwRoutineByRelId(relationObjectId);
+ 
+ 		if (fdwroutine->GetForeignRelWidth)
+ 		{
+ 			rel->max_attr = fdwroutine->GetForeignRelWidth(root, rel,
+ 														   relation,
+ 														   inhparent,
+ 														   tlist);
+ 			Assert(rel->max_attr >= RelationGetNumberOfAttributes(relation));
+ 		}
+ 	}
+ 
  	Assert(rel->max_attr >= rel->min_attr);
  	rel->attr_needed = (Relids *)
  		palloc0((rel->max_attr - rel->min_attr + 1) * sizeof(Relids));
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
new file mode 100644
index f724714..84b674c
*** a/src/backend/optimizer/util/relnode.c
--- b/src/backend/optimizer/util/relnode.c
*************** setup_simple_rel_arrays(PlannerInfo *roo
*** 80,86 ****
   *	  Construct a new RelOptInfo for a base relation or 'other' relation.
   */
  RelOptInfo *
! build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
  {
  	RelOptInfo *rel;
  	RangeTblEntry *rte;
--- 80,87 ----
   *	  Construct a new RelOptInfo for a base relation or 'other' relation.
   */
  RelOptInfo *
! build_simple_rel(PlannerInfo *root, int relid, List *tlist,
! 				 RelOptKind reloptkind)
  {
  	RelOptInfo *rel;
  	RangeTblEntry *rte;
*************** build_simple_rel(PlannerInfo *root, int 
*** 133,139 ****
  	{
  		case RTE_RELATION:
  			/* Table --- retrieve statistics from the system catalogs */
! 			get_relation_info(root, rte->relid, rte->inh, rel);
  			break;
  		case RTE_SUBQUERY:
  		case RTE_FUNCTION:
--- 134,140 ----
  	{
  		case RTE_RELATION:
  			/* Table --- retrieve statistics from the system catalogs */
! 			get_relation_info(root, rte->relid, rte->inh, tlist, rel);
  			break;
  		case RTE_SUBQUERY:
  		case RTE_FUNCTION:
*************** build_simple_rel(PlannerInfo *root, int 
*** 180,186 ****
  			if (appinfo->parent_relid != relid)
  				continue;
  
! 			(void) build_simple_rel(root, appinfo->child_relid,
  									RELOPT_OTHER_MEMBER_REL);
  		}
  	}
--- 181,187 ----
  			if (appinfo->parent_relid != relid)
  				continue;
  
! 			(void) build_simple_rel(root, appinfo->child_relid, tlist,
  									RELOPT_OTHER_MEMBER_REL);
  		}
  	}
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
new file mode 100644
index b785c26..6cd6241
*** a/src/backend/rewrite/rewriteHandler.c
--- b/src/backend/rewrite/rewriteHandler.c
*************** rewriteTargetListUD(Query *parsetree, Ra
*** 1165,1171 ****
  					Relation target_relation)
  {
  	Var		   *var;
! 	const char *attrname;
  	TargetEntry *tle;
  
  	if (target_relation->rd_rel->relkind == RELKIND_RELATION)
--- 1165,1174 ----
  					Relation target_relation)
  {
  	Var		   *var;
! 	List	   *varList;
! 	List	   *attNameList;
! 	ListCell   *cell1;
! 	ListCell   *cell2;
  	TargetEntry *tle;
  
  	if (target_relation->rd_rel->relkind == RELKIND_RELATION)
*************** rewriteTargetListUD(Query *parsetree, Ra
*** 1179,1186 ****
  					  -1,
  					  InvalidOid,
  					  0);
  
! 		attrname = "ctid";
  	}
  	else
  	{
--- 1182,1216 ----
  					  -1,
  					  InvalidOid,
  					  0);
+ 		varList = list_make1(var);
+ 		attNameList = list_make1("ctid");
+ 	}
+ 	else if (target_relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ 	{
+ 		/*
+ 		 * Emit Rowid so that executor can find the row to update or delete.
+ 		 */
+ 		var = makeVar(parsetree->resultRelation,
+ 					  RelationGetNumberOfAttributes(target_relation) + 1,
+ 					  CSTRINGOID,
+ 					  -2,
+ 					  InvalidOid,
+ 					  0);
+ 		varList = list_make1(var);
  
! 		/*
! 		 * Emit generic record Var so that executor will have the "old" view
! 		 * row to pass the RETURNING clause (or upcoming triggers).
! 		 */
! 		var = makeVar(parsetree->resultRelation,
! 					  InvalidAttrNumber,
! 					  RECORDOID,
! 					  -1,
! 					  InvalidOid,
! 					  0);
! 		varList = lappend(varList, var);
! 
! 		attNameList = list_make2("rowid", "record");
  	}
  	else
  	{
*************** rewriteTargetListUD(Query *parsetree, Ra
*** 1192,1207 ****
  							  parsetree->resultRelation,
  							  0,
  							  false);
! 
! 		attrname = "wholerow";
  	}
  
! 	tle = makeTargetEntry((Expr *) var,
! 						  list_length(parsetree->targetList) + 1,
! 						  pstrdup(attrname),
! 						  true);
! 
! 	parsetree->targetList = lappend(parsetree->targetList, tle);
  }
  
  
--- 1222,1242 ----
  							  parsetree->resultRelation,
  							  0,
  							  false);
! 		varList = list_make1(var);
! 		attNameList = list_make1("wholerow");
  	}
  
! 	/*
! 	 * Append them to targetList
! 	 */
! 	forboth (cell1, varList, cell2, attNameList)
! 	{
! 		tle = makeTargetEntry((Expr *)lfirst(cell1),
! 							  list_length(parsetree->targetList) + 1,
! 							  pstrdup((const char *)lfirst(cell2)),
! 							  true);
! 		parsetree->targetList = lappend(parsetree->targetList, tle);
! 	}
  }
  
  
diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
new file mode 100644
index 721cd25..153932a
*** a/src/include/foreign/fdwapi.h
--- b/src/include/foreign/fdwapi.h
***************
*** 14,19 ****
--- 14,20 ----
  
  #include "nodes/execnodes.h"
  #include "nodes/relation.h"
+ #include "utils/rel.h"
  
  /* To avoid including explain.h here, reference ExplainState thus: */
  struct ExplainState;
*************** struct ExplainState;
*** 22,27 ****
--- 23,33 ----
  /*
   * Callback function signatures --- see fdwhandler.sgml for more info.
   */
+ typedef AttrNumber (*GetForeignRelWidth_function) (PlannerInfo *root,
+ 												   RelOptInfo *baserel,
+ 												   Relation foreignrel,
+ 												   bool inhparent,
+ 												   List *targetList);
  
  typedef void (*GetForeignRelSize_function) (PlannerInfo *root,
  														RelOptInfo *baserel,
*************** typedef int (*AcquireSampleRowsFunc) (Re
*** 58,63 ****
--- 64,87 ----
  typedef bool (*AnalyzeForeignTable_function) (Relation relation,
  												 AcquireSampleRowsFunc *func,
  													BlockNumber *totalpages);
+ typedef List *(*PlanForeignModify_function) (PlannerInfo *root,
+ 											 ModifyTable *plan,
+ 											 Index resultRelation,
+ 											 Plan *subplan);
+ 
+ typedef void (*BeginForeignModify_function) (ModifyTableState *mtstate,
+ 											 ResultRelInfo *resultRelInfo,
+ 											 List *fdw_private,
+ 											 Plan *subplan,
+ 											 int eflags);
+ typedef int	(*ExecForeignInsert_function) (ResultRelInfo *resultRelInfo,
+ 										   HeapTuple tuple);
+ typedef int	(*ExecForeignDelete_function) (ResultRelInfo *resultRelInfo,
+ 										   Datum rowid);
+ typedef int	(*ExecForeignUpdate_function) (ResultRelInfo *resultRelInfo,
+ 										   Datum rowid,
+ 										   HeapTuple tuple);
+ typedef void (*EndForeignModify_function) (ResultRelInfo *resultRelInfo);
  
  /*
   * FdwRoutine is the struct returned by a foreign-data wrapper's handler
*************** typedef struct FdwRoutine
*** 90,95 ****
--- 114,126 ----
  	 * not provided.
  	 */
  	AnalyzeForeignTable_function AnalyzeForeignTable;
+ 	GetForeignRelWidth_function GetForeignRelWidth;
+ 	PlanForeignModify_function PlanForeignModify;
+ 	BeginForeignModify_function	BeginForeignModify;
+ 	ExecForeignInsert_function ExecForeignInsert;
+ 	ExecForeignDelete_function ExecForeignDelete;
+ 	ExecForeignUpdate_function ExecForeignUpdate;
+ 	EndForeignModify_function EndForeignModify;
  } FdwRoutine;
  
  
diff --git a/src/include/foreign/foreign.h b/src/include/foreign/foreign.h
new file mode 100644
index f8aa99e..b63000a
*** a/src/include/foreign/foreign.h
--- b/src/include/foreign/foreign.h
***************
*** 14,19 ****
--- 14,21 ----
  #define FOREIGN_H
  
  #include "nodes/parsenodes.h"
+ #include "nodes/plannodes.h"
+ #include "nodes/relation.h"
  
  
  /* Helper for obtaining username for user mapping */
*************** extern List *GetForeignColumnOptions(Oid
*** 81,84 ****
--- 83,90 ----
  extern Oid	get_foreign_data_wrapper_oid(const char *fdwname, bool missing_ok);
  extern Oid	get_foreign_server_oid(const char *servername, bool missing_ok);
  
+ extern AttrNumber get_pseudo_rowid_column(RelOptInfo *baserel,
+ 										  List *targetList);
+ extern ForeignScan *lookup_foreign_scan_plan(Plan *subplan,
+ 											 Index rtindex);
  #endif   /* FOREIGN_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
new file mode 100644
index d4911bd..18ab231
*** a/src/include/nodes/execnodes.h
--- b/src/include/nodes/execnodes.h
*************** typedef struct ProjectionInfo
*** 268,276 ****
   *						attribute numbers of the "original" tuple and the
   *						attribute numbers of the "clean" tuple.
   *	  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).
   * ----------------
   */
  typedef struct JunkFilter
--- 268,278 ----
   *						attribute numbers of the "original" tuple and the
   *						attribute numbers of the "clean" tuple.
   *	  resultSlot:		tuple slot used to hold cleaned tuple.
!  *	  jf_junkRowidNo:	not used by junkfilter code.  Can be used by caller
!  *						to remember the attno used to track a particular tuple
!  *						being updated or deleted.
   *						(execMain.c stores the "ctid" attno here).
+  *	  jf_junkRecordNo:	Also, the attno of whole-row reference.
   * ----------------
   */
  typedef struct JunkFilter
*************** typedef struct JunkFilter
*** 280,286 ****
  	TupleDesc	jf_cleanTupType;
  	AttrNumber *jf_cleanMap;
  	TupleTableSlot *jf_resultSlot;
! 	AttrNumber	jf_junkAttNo;
  } JunkFilter;
  
  /* ----------------
--- 282,289 ----
  	TupleDesc	jf_cleanTupType;
  	AttrNumber *jf_cleanMap;
  	TupleTableSlot *jf_resultSlot;
! 	AttrNumber	jf_junkRowidNo;
! 	AttrNumber	jf_junkRecordNo;
  } JunkFilter;
  
  /* ----------------
*************** typedef struct JunkFilter
*** 303,308 ****
--- 306,313 ----
   *		ConstraintExprs			array of constraint-checking expr states
   *		junkFilter				for removing junk attributes from tuples
   *		projectReturning		for computing a RETURNING list
+  *		fdwroutine				FDW callbacks if foreign table
+  *		fdw_state				opaque state of FDW module, or NULL
   * ----------------
   */
  typedef struct ResultRelInfo
*************** typedef struct ResultRelInfo
*** 320,325 ****
--- 325,332 ----
  	List	  **ri_ConstraintExprs;
  	JunkFilter *ri_junkFilter;
  	ProjectionInfo *ri_projectReturning;
+ 	struct FdwRoutine  *ri_fdwroutine;
+ 	void	   *ri_fdw_state;
  } ResultRelInfo;
  
  /* ----------------
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
new file mode 100644
index fb9a863..93481aa
*** a/src/include/nodes/plannodes.h
--- b/src/include/nodes/plannodes.h
*************** typedef struct ModifyTable
*** 175,180 ****
--- 175,181 ----
  	List	   *returningLists; /* per-target-table RETURNING tlists */
  	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
  	int			epqParam;		/* ID of Param for EvalPlanQual re-eval */
+ 	List	   *fdwPrivList;	/* private fields for foreign tables */
  } ModifyTable;
  
  /* ----------------
*************** typedef struct ForeignScan
*** 478,483 ****
--- 479,485 ----
  	List	   *fdw_exprs;		/* expressions that FDW may evaluate */
  	List	   *fdw_private;	/* private data for FDW */
  	bool		fsSystemCol;	/* true if any "system column" is needed */
+ 	bool		fsPseudoCol;	/* true if any "pseudo column" is needed */
  } ForeignScan;
  
  
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
new file mode 100644
index de889fb..adfc93d
*** a/src/include/optimizer/pathnode.h
--- b/src/include/optimizer/pathnode.h
*************** extern Path *reparameterize_path(Planner
*** 133,139 ****
   */
  extern void setup_simple_rel_arrays(PlannerInfo *root);
  extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid,
! 				 RelOptKind reloptkind);
  extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid);
  extern RelOptInfo *find_join_rel(PlannerInfo *root, Relids relids);
  extern RelOptInfo *build_join_rel(PlannerInfo *root,
--- 133,139 ----
   */
  extern void setup_simple_rel_arrays(PlannerInfo *root);
  extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid,
! 			   List *tlist, RelOptKind reloptkind);
  extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid);
  extern RelOptInfo *find_join_rel(PlannerInfo *root, Relids relids);
  extern RelOptInfo *build_join_rel(PlannerInfo *root,
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
new file mode 100644
index e0d04db..c5fce41
*** a/src/include/optimizer/plancat.h
--- b/src/include/optimizer/plancat.h
*************** extern PGDLLIMPORT get_relation_info_hoo
*** 26,32 ****
  
  
  extern void get_relation_info(PlannerInfo *root, Oid relationObjectId,
! 				  bool inhparent, RelOptInfo *rel);
  
  extern void estimate_rel_size(Relation rel, int32 *attr_widths,
  				  BlockNumber *pages, double *tuples, double *allvisfrac);
--- 26,32 ----
  
  
  extern void get_relation_info(PlannerInfo *root, Oid relationObjectId,
! 				  bool inhparent, List *tlist, RelOptInfo *rel);
  
  extern void estimate_rel_size(Relation rel, int32 *attr_widths,
  				  BlockNumber *pages, double *tuples, double *allvisfrac);
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
new file mode 100644
index 0fe696c..746a603
*** a/src/include/optimizer/planmain.h
--- b/src/include/optimizer/planmain.h
*************** extern SetOp *make_setop(SetOpCmd cmd, S
*** 79,85 ****
  		   long numGroups, double outputRows);
  extern Result *make_result(PlannerInfo *root, List *tlist,
  			Node *resconstantqual, Plan *subplan);
! extern ModifyTable *make_modifytable(CmdType operation, bool canSetTag,
  				 List *resultRelations, List *subplans, List *returningLists,
  				 List *rowMarks, int epqParam);
  extern bool is_projection_capable_plan(Plan *plan);
--- 79,86 ----
  		   long numGroups, double outputRows);
  extern Result *make_result(PlannerInfo *root, List *tlist,
  			Node *resconstantqual, Plan *subplan);
! 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);
*************** extern bool is_projection_capable_plan(P
*** 90,96 ****
  extern int	from_collapse_limit;
  extern int	join_collapse_limit;
  
! extern void add_base_rels_to_query(PlannerInfo *root, Node *jtnode);
  extern void build_base_rel_tlists(PlannerInfo *root, List *final_tlist);
  extern void add_vars_to_targetlist(PlannerInfo *root, List *vars,
  					   Relids where_needed, bool create_new_ph);
--- 91,98 ----
  extern int	from_collapse_limit;
  extern int	join_collapse_limit;
  
! extern void add_base_rels_to_query(PlannerInfo *root, List *tlist,
! 								   Node *jtnode);
  extern void build_base_rel_tlists(PlannerInfo *root, List *final_tlist);
  extern void add_vars_to_targetlist(PlannerInfo *root, List *vars,
  					   Relids where_needed, bool create_new_ph);
#42Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Albe Laurenz (#41)
2 attachment(s)
Re: [v9.3] writable foreign tables

2012/12/11 Albe Laurenz <laurenz.albe@wien.gv.at>:

Kohei KaiGai wrote:

Weird, that fails for me.

Both of the troubles you reported was fixed with attached patch.
I also added relevant test cases into regression test, please check it.

It passes the regression tests, and solves the problems I found.

I came up with one more query that causes a problem:

CREATE TABLE test(
id integer PRIMARY KEY,
val text NOT NULL
);

INSERT INTO test(id, val) VALUES (1, 'one');

CREATE FOREIGN TABLE rtest(
id integer not null,
val text not null
) SERVER loopback OPTIONS (relname 'test');

/* loopback points to the same database */

WITH ch AS (
UPDATE test
SET val='changed'
RETURNING id
) UPDATE rtest
SET val='new'
FROM ch
WHERE rtest.id = ch.id;

This causes a deadlock, but one that is not detected;
the query just keeps hanging.

The UPDATE in the CTE has the rows locked, so the
SELECT ... FOR UPDATE issued via the FDW connection will hang
indefinitely.

I wonder if that's just a pathological corner case that we shouldn't
worry about. Loopback connections for FDWs themselves might not
be so rare, for example as a substitute for autonomous subtransactions.

I guess it is not easily possible to detect such a situation or
to do something reasonable about it.

It is not avoidable problem due to the nature of distributed database system,
not only loopback scenario.

In my personal opinion, I'd like to wait for someone implements distributed
lock/transaction manager on top of the background worker framework being
recently committed, to intermediate lock request.
However, it will take massive amount of efforts to existing lock/transaction
management layer, not only enhancement of FDW APIs. It is obviously out
of scope in this patch.

So, I'd like to suggest authors of FDW that support writable features to put
mention about possible deadlock scenario in their documentation.
At least, above writable CTE example is a situation that two different sessions
concurrently update the "test" relation, thus, one shall be blocked.

I took a brief look at the documentation; that will need some more
work.

Yep, I believe it should be revised prior to this patch being committed.

I tried to overhaul the documentation, see the attached patch.

There was one thing that I was not certain of:
You say that for writable foreign tables, BeginForeignModify
and EndForeignModify *must* be implemented.
I thought that these were optional, and if you can do your work
with just, say, ExecForeignDelete, you could do that.

Yes, that's right. What I wrote was incorrect.
If FDW driver does not require any state during modification of
foreign tables, indeed, these are not mandatory handler.

I left that paragraph roughly as it is, please change it if
appropriate.

I also changed the misspelled "resultRelaion" and updated a
few comments.

May I suggest to split the patch in two parts, one for
all the parts that affect postgres_fdw and one for the rest?
That might make the committer's work easier, since
postgres_fdw is not applied (yet).

OK. I split the patch into two portion, part-1 is the APIs relevant
patch, part-2 is relevant to postgres_fdw patch.

In addition, I adjusted the APIs a bit.
Even though the rowid pseudo-column is constructed as cstring
data type, the API delivered it as Datum type. We have no reason
why not to case prior to invoke Update or Delete handler.
At the beginning, I constructed the rowid pseudo-column using
INTERNALOID, but it made troubles when fetched tuples are
materialized prior to table modification.
So, the "rowid" argument of them are re-defined as "const char *".

Also, I found a possible bug when EXPLAIN command prints plan
tree with qualifiers that reference pseudo-columns. In this case,
it raise an error with "invalid attnum %d ...".
So, I adjusted the logic to reference eref alias when get_attname
returns NULL towards foreign-tables. It also required FDW driver
also need to set up rte->eref alias for each pseudo-column.

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

Attachments:

pgsql-v9.3-writable-fdw-poc.v8.part-2.patchapplication/octet-stream; name=pgsql-v9.3-writable-fdw-poc.v8.part-2.patchDownload
 contrib/postgres_fdw/connection.c              |   70 +-
 contrib/postgres_fdw/connection.h              |    8 +-
 contrib/postgres_fdw/deparse.c                 |  126 ++-
 contrib/postgres_fdw/expected/postgres_fdw.out | 1098 ++++++++++++++++++++++++
 contrib/postgres_fdw/postgres_fdw.c            |  414 ++++++++-
 contrib/postgres_fdw/postgres_fdw.h            |    6 +-
 contrib/postgres_fdw/sql/postgres_fdw.sql      |   16 +
 7 files changed, 1706 insertions(+), 32 deletions(-)

diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index eab8b87..9ca7fbf 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -43,7 +43,7 @@ typedef struct ConnCacheEntry
 	Oid				serverid;	/* oid of foreign server */
 	Oid				userid;		/* oid of local user */
 
-	bool			use_tx;		/* true when using remote transaction */
+	int				conntx;		/* one of PGSQL_FDW_CONNTX_* */
 	int				refs;		/* reference counter */
 	PGconn		   *conn;		/* foreign server connection */
 } ConnCacheEntry;
@@ -65,6 +65,8 @@ cleanup_connection(ResourceReleasePhase phase,
 static PGconn *connect_pg_server(ForeignServer *server, UserMapping *user);
 static void begin_remote_tx(PGconn *conn);
 static void abort_remote_tx(PGconn *conn);
+static void commit_remote_tx(PGconn *conn);
+static void deallocate_remote_prepare(PGconn *conn);
 
 /*
  * Get a PGconn which can be used to execute foreign query on the remote
@@ -80,7 +82,7 @@ static void abort_remote_tx(PGconn *conn);
  * FDW object to invalidate already established connections.
  */
 PGconn *
-GetConnection(ForeignServer *server, UserMapping *user, bool use_tx)
+GetConnection(ForeignServer *server, UserMapping *user, int conntx)
 {
 	bool			found;
 	ConnCacheEntry *entry;
@@ -126,7 +128,7 @@ GetConnection(ForeignServer *server, UserMapping *user, bool use_tx)
 	entry = hash_search(ConnectionHash, &key, HASH_ENTER, &found);
 	if (!found)
 	{
-		entry->use_tx = false;
+		entry->conntx = PGSQL_FDW_CONNTX_NONE;
 		entry->refs = 0;
 		entry->conn = NULL;
 	}
@@ -162,7 +164,7 @@ GetConnection(ForeignServer *server, UserMapping *user, bool use_tx)
 		{
 			/* Clear connection cache entry on error case. */
 			PQfinish(entry->conn);
-			entry->use_tx = false;
+			entry->conntx = PGSQL_FDW_CONNTX_NONE;
 			entry->refs = 0;
 			entry->conn = NULL;
 			PG_RE_THROW();
@@ -182,10 +184,11 @@ GetConnection(ForeignServer *server, UserMapping *user, bool use_tx)
 	 * are in.  We need to remember whether this connection uses remote
 	 * transaction to abort it when this connection is released completely.
 	 */
-	if (use_tx && !entry->use_tx)
+	if (conntx > entry->conntx)
 	{
-		begin_remote_tx(entry->conn);
-		entry->use_tx = use_tx;
+		if (entry->conntx == PGSQL_FDW_CONNTX_NONE)
+			begin_remote_tx(entry->conn);
+		entry->conntx = conntx;
 	}
 
 	return entry->conn;
@@ -355,12 +358,45 @@ abort_remote_tx(PGconn *conn)
 	PQclear(res);
 }
 
+static void
+commit_remote_tx(PGconn *conn)
+{
+	PGresult	   *res;
+
+	elog(DEBUG3, "committing remote transaction");
+
+	res = PQexec(conn, "COMMIT TRANSACTION");
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		PQclear(res);
+		elog(ERROR, "could not commit transaction: %s", PQerrorMessage(conn));
+	}
+	PQclear(res);
+}
+
+static void
+deallocate_remote_prepare(PGconn *conn)
+{
+	PGresult	   *res;
+
+	elog(DEBUG3, "deallocating remote prepares");
+
+	res = PQexec(conn, "DEALLOCATE PREPARE ALL");
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		PQclear(res);
+		elog(ERROR, "could not deallocate prepared statement: %s",
+			 PQerrorMessage(conn));
+	}
+	PQclear(res);
+}
+
 /*
  * Mark the connection as "unused", and close it if the caller was the last
  * user of the connection.
  */
 void
-ReleaseConnection(PGconn *conn)
+ReleaseConnection(PGconn *conn, bool is_abort)
 {
 	HASH_SEQ_STATUS		scan;
 	ConnCacheEntry	   *entry;
@@ -412,7 +448,7 @@ ReleaseConnection(PGconn *conn)
 			 PQtransactionStatus(conn) == PQTRANS_INERROR ? "INERROR" :
 			 "UNKNOWN");
 		PQfinish(conn);
-		entry->use_tx = false;
+		entry->conntx = PGSQL_FDW_CONNTX_NONE;
 		entry->refs = 0;
 		entry->conn = NULL;
 		return;
@@ -430,10 +466,16 @@ ReleaseConnection(PGconn *conn)
 	 * If this connection uses remote transaction and there is no user other
 	 * than the caller, abort the remote transaction and forget about it.
 	 */
-	if (entry->use_tx && entry->refs == 0)
+	if (entry->conntx > PGSQL_FDW_CONNTX_NONE && entry->refs == 0)
 	{
-		abort_remote_tx(conn);
-		entry->use_tx = false;
+		if (entry->conntx > PGSQL_FDW_CONNTX_READ_ONLY)
+			deallocate_remote_prepare(conn);
+		if (is_abort || entry->conntx == PGSQL_FDW_CONNTX_READ_ONLY)
+			abort_remote_tx(conn);
+		else
+			commit_remote_tx(conn);
+
+		entry->conntx = PGSQL_FDW_CONNTX_NONE;
 	}
 }
 
@@ -485,7 +527,7 @@ cleanup_connection(ResourceReleasePhase phase,
 		elog(DEBUG3, "discard postgres_fdw connection %p due to resowner cleanup",
 			 entry->conn);
 		PQfinish(entry->conn);
-		entry->use_tx = false;
+		entry->conntx = PGSQL_FDW_CONNTX_NONE;
 		entry->refs = 0;
 		entry->conn = NULL;
 	}
@@ -597,7 +639,7 @@ postgres_fdw_disconnect(PG_FUNCTION_ARGS)
 
 	/* Discard cached connection, and clear reference counter. */
 	PQfinish(entry->conn);
-	entry->use_tx = false;
+	entry->conntx = PGSQL_FDW_CONNTX_NONE;
 	entry->refs = 0;
 	entry->conn = NULL;
 
diff --git a/contrib/postgres_fdw/connection.h b/contrib/postgres_fdw/connection.h
index 4c9d850..f97cc8a 100644
--- a/contrib/postgres_fdw/connection.h
+++ b/contrib/postgres_fdw/connection.h
@@ -16,10 +16,14 @@
 #include "foreign/foreign.h"
 #include "libpq-fe.h"
 
+#define PGSQL_FDW_CONNTX_NONE			0
+#define PGSQL_FDW_CONNTX_READ_ONLY		1
+#define PGSQL_FDW_CONNTX_READ_WRITE		2
+
 /*
  * Connection management
  */
-PGconn *GetConnection(ForeignServer *server, UserMapping *user, bool use_tx);
-void ReleaseConnection(PGconn *conn);
+PGconn *GetConnection(ForeignServer *server, UserMapping *user, int conntx);
+void ReleaseConnection(PGconn *conn, bool is_abort);
 
 #endif /* CONNECTION_H */
diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
index 69e6a3e..9e09429 100644
--- a/contrib/postgres_fdw/deparse.c
+++ b/contrib/postgres_fdw/deparse.c
@@ -12,6 +12,7 @@
  */
 #include "postgres.h"
 
+#include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/transam.h"
 #include "catalog/pg_class.h"
@@ -86,9 +87,11 @@ void
 deparseSimpleSql(StringInfo buf,
 				 PlannerInfo *root,
 				 RelOptInfo *baserel,
-				 List *local_conds)
+				 List *local_conds,
+				 AttrNumber anum_rowid)
 {
 	RangeTblEntry *rte;
+	Relation	rel;
 	ListCell   *lc;
 	StringInfoData	foreign_relname;
 	bool		first;
@@ -125,6 +128,24 @@ deparseSimpleSql(StringInfo buf,
 	}
 
 	/*
+	 * XXX - When this foreign table is target relation and RETURNING
+	 * clause reference some column, we have to mark these columns as
+	 * in-use. It is needed to support DELETE command, because INSERT
+	 * and UPDATE implicitly add references to all the regular columns
+	 * on baserel->reltargetlist.
+	 */
+	if (root->parse->resultRelation == baserel->relid &&
+		root->parse->returningList)
+	{
+		List   *attrs;
+
+		attrs = pull_var_clause((Node *) root->parse->returningList,
+								PVC_RECURSE_AGGREGATES,
+                                PVC_RECURSE_PLACEHOLDERS);
+		attr_used = list_union(attr_used, attrs);
+	}
+
+	/*
 	 * deparse SELECT clause
 	 *
 	 * List attributes which are in either target list or local restriction.
@@ -136,9 +157,10 @@ deparseSimpleSql(StringInfo buf,
 	 */
 	appendStringInfo(buf, "SELECT ");
 	rte = root->simple_rte_array[baserel->relid];
+	rel = heap_open(rte->relid, NoLock);
 	attr_used = list_union(attr_used, baserel->reltargetlist);
 	first = true;
-	for (attr = 1; attr <= baserel->max_attr; attr++)
+	for (attr = 1; attr <= RelationGetNumberOfAttributes(rel); attr++)
 	{
 		Var		   *var = NULL;
 		ListCell   *lc;
@@ -167,6 +189,10 @@ deparseSimpleSql(StringInfo buf,
 		else
 			appendStringInfo(buf, "NULL");
 	}
+	if (anum_rowid != InvalidAttrNumber)
+		appendStringInfo(buf, "%sctid", (first ? "" : ","));
+
+	heap_close(rel, NoLock);
 	appendStringInfoChar(buf, ' ');
 
 	/*
@@ -283,6 +309,102 @@ deparseAnalyzeSql(StringInfo buf, Relation rel)
 }
 
 /*
+ * deparse remote INSERT statement
+ */
+void
+deparseInsertSql(StringInfo buf, PlannerInfo *root, Index rtindex)
+{
+	RangeTblEntry  *rte = root->simple_rte_array[rtindex];
+	Relation		frel = heap_open(rte->relid, NoLock);
+	int				i, j, nattrs = RelationGetNumberOfAttributes(frel);
+	bool			is_first = true;
+
+	appendStringInfo(buf, "INSERT INTO ");
+	deparseRelation(buf, rte);
+	appendStringInfo(buf, "(");
+
+	for (i=0; i < nattrs; i++)
+	{
+		Form_pg_attribute attr = RelationGetDescr(frel)->attrs[i];
+		Var		var;
+
+		if (attr->attisdropped)
+			continue;
+
+		if (!is_first)
+			appendStringInfo(buf, ",");
+
+		var.varno = rtindex;
+		var.varattno = attr->attnum;
+		deparseVar(buf, &var, root);
+		is_first = false;
+	}
+	appendStringInfo(buf, ") VALUES (");
+
+	for (i=0, j=1; i < nattrs; i++)
+	{
+		Form_pg_attribute attr = RelationGetDescr(frel)->attrs[i];
+
+		if (attr->attisdropped)
+			continue;
+
+		appendStringInfo(buf, "%s$%d", (j == 1 ? "" : ","), j);
+		j++;
+	}
+	appendStringInfo(buf, ")");
+	heap_close(frel, NoLock);
+}
+
+/*
+ * deparse remote UPDATE statement
+ */
+void
+deparseUpdateSql(StringInfo buf, PlannerInfo *root, Index rtindex)
+{
+	RangeTblEntry  *rte = root->simple_rte_array[rtindex];
+	Relation		frel = heap_open(rte->relid, NoLock);
+	int				i, j, nattrs = RelationGetNumberOfAttributes(frel);
+	bool			is_first = true;
+
+	appendStringInfo(buf, "UPDATE ");
+	deparseRelation(buf, rte);
+	appendStringInfo(buf, " SET ");
+
+	for (i=0, j=2; i < nattrs; i++)
+	{
+		Form_pg_attribute attr = RelationGetDescr(frel)->attrs[i];
+		Var		var;
+
+		if (attr->attisdropped)
+			continue;
+
+		if (!is_first)
+			appendStringInfo(buf, ",");
+
+		var.varno = rtindex;
+		var.varattno = attr->attnum;
+		deparseVar(buf, &var, root);
+		appendStringInfo(buf, "=$%d", j++);
+		is_first = false;
+	}
+	appendStringInfo(buf, " WHERE ctid=$1");
+	heap_close(frel, NoLock);
+}
+
+/*
+ * deparse remote DELETE statement
+ */
+void
+deparseDeleteSql(StringInfo buf, PlannerInfo *root, Index rtindex)
+{
+	RangeTblEntry  *rte = root->simple_rte_array[rtindex];
+
+	appendStringInfo(buf, "DELETE FROM ");
+	deparseRelation(buf, rte);
+	appendStringInfo(buf, " WHERE ctid = $1");
+}
+
+/*
  * Deparse given expression into buf.  Actual string operation is delegated to
  * node-type-specific functions.
  *
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index f81c727..25269a8 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -736,6 +736,1104 @@ SELECT srvname FROM postgres_fdw_connections;
 (0 rows)
 
 -- ===================================================================
+-- test for writable foreign table stuff (PoC stage now)
+-- ===================================================================
+EXPLAIN(verbose) INSERT INTO ft2 (c1,c2,c3) (SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20);
+                                                                                                          QUERY PLAN                                                                                                           
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Insert on public.ft2  (cost=100.00..100.03 rows=1 width=40)
+   ->  Subquery Scan on "*SELECT*"  (cost=100.00..100.03 rows=1 width=40)
+         Output: NULL::integer, "*SELECT*"."?column?", "*SELECT*"."?column?", "*SELECT*"."?column?", NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying, NULL::bpchar, NULL::user_enum
+         ->  Limit  (cost=100.00..100.02 rows=1 width=40)
+               Output: ((ft2_1.c1 + 1000)), ((ft2_1.c2 + 100)), ((ft2_1.c3 || ft2_1.c3))
+               ->  Foreign Scan on public.ft2 ft2_1  (cost=100.00..100.02 rows=1 width=40)
+                     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"
+(8 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) 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  (cost=200.00..200.04 rows=1 width=344)
+   ->  Nested Loop  (cost=200.00..200.04 rows=1 width=344)
+         Output: NULL::integer, ft2.c1, (ft2.c2 + 500), (ft2.c3 || '_update9'::text), ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid, ft2.*, ft1.*
+         Join Filter: (ft2.c2 = ft1.c1)
+         ->  Foreign Scan on public.ft2  (cost=100.00..100.01 rows=1 width=232)
+               Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid, ft2.*
+               Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8,ctid FROM "S 1"."T 1" FOR UPDATE
+         ->  Foreign Scan on public.ft1  (cost=100.00..100.01 rows=1 width=116)
+               Output: ft1.*, ft1.c1
+               Remote SQL: SELECT "C 1", NULL, NULL, NULL, NULL, NULL, NULL, NULL FROM "S 1"."T 1" WHERE ((("C 1" OPERATOR(pg_catalog.%) 10) OPERATOR(pg_catalog.=) 9))
+(10 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) DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2;
+                                                                               QUERY PLAN                                                                               
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Delete on public.ft2  (cost=200.00..200.03 rows=1 width=256)
+   ->  Nested Loop  (cost=200.00..200.03 rows=1 width=256)
+         Output: ft2.ctid, ft2.*, ft1.*
+         Join Filter: (ft2.c2 = ft1.c1)
+         ->  Foreign Scan on public.ft2  (cost=100.00..100.01 rows=1 width=148)
+               Output: ft2.ctid, ft2.*, ft2.c2
+               Remote SQL: SELECT NULL, c2, NULL, NULL, NULL, NULL, NULL, NULL,ctid FROM "S 1"."T 1" FOR UPDATE
+         ->  Foreign Scan on public.ft1  (cost=100.00..100.01 rows=1 width=116)
+               Output: ft1.*, ft1.c1
+               Remote SQL: SELECT "C 1", NULL, NULL, NULL, NULL, NULL, NULL, NULL FROM "S 1"."T 1" WHERE ((("C 1" OPERATOR(pg_catalog.%) 10) OPERATOR(pg_catalog.=) 2))
+(10 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)
+
+-- ===================================================================
 -- cleanup
 -- ===================================================================
 DROP OPERATOR === (int, int) CASCADE;
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 6b870ab..75439b1 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -27,6 +27,7 @@
 #include "optimizer/pathnode.h"
 #include "optimizer/planmain.h"
 #include "optimizer/restrictinfo.h"
+#include "parser/parsetree.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -59,6 +60,7 @@ typedef struct PostgresFdwPlanState {
 	List		   *param_conds;
 	List		   *local_conds;
 	int				width;			/* obtained by remote EXPLAIN */
+	AttrNumber		anum_rowid;
 
 	/* Cached catalog information. */
 	ForeignTable   *table;
@@ -150,6 +152,20 @@ typedef struct PostgresAnalyzeState
 } PostgresAnalyzeState;
 
 /*
+ * Describes a state of modify request for a foreign table
+ */
+typedef struct PostgresFdwModifyState
+{
+	PGconn	   *conn;
+	char	   *query;
+	char	   *p_name;
+	int			p_nums;
+	Oid		   *p_types;
+	FmgrInfo   *p_flinfo;
+	MemoryContext	es_query_cxt;
+} PostgresFdwModifyState;
+
+/*
  * SQL functions
  */
 extern Datum postgres_fdw_handler(PG_FUNCTION_ARGS);
@@ -158,6 +174,11 @@ PG_FUNCTION_INFO_V1(postgres_fdw_handler);
 /*
  * FDW callback routines
  */
+static AttrNumber postgresGetForeignRelWidth(PlannerInfo *root,
+											 RelOptInfo *baserel,
+											 Relation foreignrel,
+											 bool inhparent,
+											 List *targetList);
 static void postgresGetForeignRelSize(PlannerInfo *root,
 									  RelOptInfo *baserel,
 									  Oid foreigntableid);
@@ -179,6 +200,23 @@ static void postgresEndForeignScan(ForeignScanState *node);
 static bool postgresAnalyzeForeignTable(Relation relation,
 										AcquireSampleRowsFunc *func,
 										BlockNumber *totalpages);
+static List *postgresPlanForeignModify(PlannerInfo *root,
+									   ModifyTable *plan,
+									   Index resultRelation,
+									   Plan *subplan);
+static void postgresBeginForeignModify(ModifyTableState *mtstate,
+									   ResultRelInfo *resultRelInfo,
+									   List *fdw_private,
+									   Plan *subplan,
+									   int eflags);
+static int postgresExecForeignInsert(ResultRelInfo *resultRelInfo,
+									 HeapTuple tuple);
+static int postgresExecForeignDelete(ResultRelInfo *resultRelInfo,
+									 const char *rowid);
+static int postgresExecForeignUpdate(ResultRelInfo *resultRelInfo,
+									 const char *rowid,
+									 HeapTuple tuple);
+static void postgresEndForeignModify(ResultRelInfo *resultRelInfo);
 
 /*
  * Helper functions
@@ -231,6 +269,7 @@ postgres_fdw_handler(PG_FUNCTION_ARGS)
 	FdwRoutine	*routine = makeNode(FdwRoutine);
 
 	/* Required handler functions. */
+	routine->GetForeignRelWidth = postgresGetForeignRelWidth;
 	routine->GetForeignRelSize = postgresGetForeignRelSize;
 	routine->GetForeignPaths = postgresGetForeignPaths;
 	routine->GetForeignPlan = postgresGetForeignPlan;
@@ -239,6 +278,12 @@ postgres_fdw_handler(PG_FUNCTION_ARGS)
 	routine->IterateForeignScan = postgresIterateForeignScan;
 	routine->ReScanForeignScan = postgresReScanForeignScan;
 	routine->EndForeignScan = postgresEndForeignScan;
+	routine->PlanForeignModify = postgresPlanForeignModify;
+	routine->BeginForeignModify = postgresBeginForeignModify;
+	routine->ExecForeignInsert = postgresExecForeignInsert;
+	routine->ExecForeignDelete = postgresExecForeignDelete;
+	routine->ExecForeignUpdate = postgresExecForeignUpdate;
+	routine->EndForeignModify = postgresEndForeignModify;
 
 	/* Optional handler functions. */
 	routine->AnalyzeForeignTable = postgresAnalyzeForeignTable;
@@ -247,6 +292,34 @@ postgres_fdw_handler(PG_FUNCTION_ARGS)
 }
 
 /*
+ * postgresGetForeignRelWidth
+ *		Informs how many columns (including pseudo ones) are needed.
+ */
+static AttrNumber
+postgresGetForeignRelWidth(PlannerInfo *root,
+						   RelOptInfo *baserel,
+						   Relation foreignrel,
+						   bool inhparent,
+						   List *targetList)
+{
+	PostgresFdwPlanState *fpstate = palloc0(sizeof(PostgresFdwPlanState));
+
+	baserel->fdw_private = fpstate;
+
+	/* does rowid pseudo-column is required? */
+	fpstate->anum_rowid = get_pseudo_rowid_column(baserel, targetList);
+	if (fpstate->anum_rowid != InvalidAttrNumber)
+	{
+		RangeTblEntry *rte = rt_fetch(baserel->relid,
+									  root->parse->rtable);
+		rte->eref->colnames = lappend(rte->eref->colnames,
+									  makeString("ctid"));
+		return fpstate->anum_rowid;
+	}
+	return RelationGetNumberOfAttributes(foreignrel);
+}
+
+/*
  * postgresGetForeignRelSize
  *		Estimate # of rows and width of the result of the scan
  *
@@ -283,7 +356,7 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	 * We use PostgresFdwPlanState to pass various information to subsequent
 	 * functions.
 	 */
-	fpstate = palloc0(sizeof(PostgresFdwPlanState));
+	fpstate = baserel->fdw_private;
 	initStringInfo(&fpstate->sql);
 	sql = &fpstate->sql;
 
@@ -320,10 +393,9 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	 */
 	classifyConditions(root, baserel, &remote_conds, &param_conds,
 					   &local_conds);
-	deparseSimpleSql(sql, root, baserel, local_conds);
+	deparseSimpleSql(sql, root, baserel, local_conds, fpstate->anum_rowid);
 	if (list_length(remote_conds) > 0)
 		appendWhereClause(sql, true, remote_conds, root);
-	elog(DEBUG3, "Query SQL: %s", sql->data);
 
 	/*
 	 * If the table or the server is configured to use remote EXPLAIN, connect
@@ -337,10 +409,10 @@ postgresGetForeignRelSize(PlannerInfo *root,
 		PGconn		   *conn;
 
 		user = GetUserMapping(GetOuterUserId(), server->serverid);
-		conn = GetConnection(server, user, false);
+		conn = GetConnection(server, user, PGSQL_FDW_CONNTX_NONE);
 		get_remote_estimate(sql->data, conn, &rows, &width,
 							&startup_cost, &total_cost);
-		ReleaseConnection(conn);
+		ReleaseConnection(conn, false);
 
 		/*
 		 * Estimate selectivity of conditions which are not used in remote
@@ -391,7 +463,6 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	fpstate->width = width;
 	fpstate->table = table;
 	fpstate->server = server;
-	baserel->fdw_private = (void *) fpstate;
 }
 
 /*
@@ -592,7 +663,7 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 	table = GetForeignTable(relid);
 	server = GetForeignServer(table->serverid);
 	user = GetUserMapping(GetOuterUserId(), server->serverid);
-	conn = GetConnection(server, user, true);
+	conn = GetConnection(server, user, PGSQL_FDW_CONNTX_READ_ONLY);
 	festate->conn = conn;
 
 	/* Result will be filled in first Iterate call. */
@@ -724,7 +795,7 @@ postgresEndForeignScan(ForeignScanState *node)
 	 * end of the scan to make the lifespan of remote transaction same as the
 	 * local query.
 	 */
-	ReleaseConnection(festate->conn);
+	ReleaseConnection(festate->conn, false);
 	festate->conn = NULL;
 
 	/* Discard fetch results */
@@ -790,7 +861,7 @@ get_remote_estimate(const char *sql, PGconn *conn,
 		PQclear(res);
 
 		/* Release connection and let connection manager cleanup. */
-		ReleaseConnection(conn);
+		ReleaseConnection(conn, true);
 
 		PG_RE_THROW();
 	}
@@ -947,7 +1018,7 @@ execute_query(ForeignScanState *node)
 		PQclear(res);
 
 		/* Release connection and let connection manager cleanup. */
-		ReleaseConnection(conn);
+		ReleaseConnection(conn, true);
 
 		/* propagate error */
 		PG_RE_THROW();
@@ -1105,6 +1176,8 @@ postgres_fdw_error_callback(void *arg)
 
 	relname = get_rel_name(errpos->relid);
 	colname = get_attname(errpos->relid, errpos->cur_attno);
+	if (!colname)
+		colname = "pseudo-column";
 	errcontext("column %s of foreign table %s",
 			   quote_identifier(colname), quote_identifier(relname));
 }
@@ -1172,7 +1245,7 @@ postgresAcquireSampleRowsFunc(Relation relation, int elevel,
 	table = GetForeignTable(relation->rd_id);
 	server = GetForeignServer(table->serverid);
 	user = GetUserMapping(GetOuterUserId(), server->serverid);
-	conn = GetConnection(server, user, true);
+	conn = GetConnection(server, user, PGSQL_FDW_CONNTX_READ_ONLY);
 
 	/*
 	 * Acquire sample rows from the result set.
@@ -1239,13 +1312,13 @@ postgresAcquireSampleRowsFunc(Relation relation, int elevel,
 		PQclear(res);
 
 		/* Release connection and let connection manager cleanup. */
-		ReleaseConnection(conn);
+		ReleaseConnection(conn, true);
 
 		PG_RE_THROW();
 	}
 	PG_END_TRY();
 
-	ReleaseConnection(conn);
+	ReleaseConnection(conn, false);
 
 	/* We assume that we have no dead tuple. */
 	*totaldeadrows = 0.0;
@@ -1429,3 +1502,318 @@ analyze_row_processor(PGresult *res, PostgresAnalyzeState *astate, bool first)
 
 	return;
 }
+
+static List *
+postgresPlanForeignModify(PlannerInfo *root,
+						  ModifyTable *plan,
+						  Index resultRelation,
+						  Plan *subplan)
+{
+	CmdType			operation = plan->operation;
+	StringInfoData	sql;
+
+	initStringInfo(&sql);
+
+	/*
+	 * XXX - In case of UPDATE or DELETE commands are quite "simple",
+	 * we will be able to execute raw UPDATE or DELETE statement at
+	 * the stage of scan, instead of combination SELECT ... FOR UPDATE
+	 * and either of UPDATE or DELETE commands.
+	 * It should be an idea of optimization in the future version.
+	 *
+	 * XXX - FOR UPDATE should be appended on the remote query of scan
+	 * stage to avoid unexpected concurrent update on the target rows.
+	 */
+	if (operation == CMD_UPDATE || operation == CMD_DELETE)
+	{
+		ForeignScan	   *fscan;
+		Value		   *select_sql;
+
+		fscan = lookup_foreign_scan_plan(subplan, resultRelation);
+		if (!fscan)
+			elog(ERROR, "no underlying scan plan found in subplan tree");
+
+		select_sql = list_nth(fscan->fdw_private,
+							  FdwPrivateSelectSql);
+		appendStringInfo(&sql, "%s FOR UPDATE", strVal(select_sql));
+		strVal(select_sql) = pstrdup(sql.data);
+
+		resetStringInfo(&sql);
+	}
+
+	switch (operation)
+	{
+		case CMD_INSERT:
+			deparseInsertSql(&sql, root, resultRelation);
+			elog(DEBUG3, "Remote INSERT query: %s", sql.data);
+			break;
+		case CMD_UPDATE:
+			deparseUpdateSql(&sql, root, resultRelation);
+			elog(DEBUG3, "Remote UPDATE query: %s", sql.data);
+			break;
+		case CMD_DELETE:
+			deparseDeleteSql(&sql, root, resultRelation);
+			elog(DEBUG3, "Remote DELETE query: %s", sql.data);
+			break;
+		default:
+			elog(ERROR, "unexpected operation: %d", (int) operation);
+	}
+	return list_make1(makeString(sql.data));
+}
+
+static void
+postgresBeginForeignModify(ModifyTableState *mtstate,
+						   ResultRelInfo *resultRelInfo,
+						   List *fdw_private,
+						   Plan *subplan,
+						   int eflags)
+{
+	PostgresFdwModifyState *fmstate;
+	CmdType			operation = mtstate->operation;
+	Relation		frel = resultRelInfo->ri_RelationDesc;
+	AttrNumber		n_params = RelationGetNumberOfAttributes(frel) + 1;
+	ForeignTable   *ftable;
+	ForeignServer  *fserver;
+	UserMapping	   *fuser;
+	Oid				out_func_oid;
+	bool			isvarlena;
+
+	/*
+	 * Construct PostgresFdwExecutionState
+	 */
+	fmstate = palloc0(sizeof(PostgresFdwExecutionState));
+
+	ftable = GetForeignTable(RelationGetRelid(frel));
+	fserver = GetForeignServer(ftable->serverid);
+	fuser = GetUserMapping(GetOuterUserId(), fserver->serverid);
+
+	fmstate->query = strVal(linitial(fdw_private));
+	fmstate->conn = GetConnection(fserver, fuser,
+								  PGSQL_FDW_CONNTX_READ_WRITE);
+	fmstate->p_name = NULL;
+	fmstate->p_types = palloc0(sizeof(Oid) * n_params);
+	fmstate->p_flinfo = palloc0(sizeof(FmgrInfo) * n_params);
+
+	/* 1st parameter should be ctid on UPDATE or DELETE */
+	if (operation == CMD_UPDATE || operation == CMD_DELETE)
+	{
+		fmstate->p_types[fmstate->p_nums] = TIDOID;
+		getTypeOutputInfo(TIDOID, &out_func_oid, &isvarlena);
+		fmgr_info(out_func_oid, &fmstate->p_flinfo[fmstate->p_nums]);
+		fmstate->p_nums++;
+	}
+	/* following parameters should be regular columns */
+	if (operation == CMD_UPDATE || operation == CMD_INSERT)
+	{
+		AttrNumber	index;
+
+		for (index=0; index < RelationGetNumberOfAttributes(frel); index++)
+		{
+			Form_pg_attribute	attr = RelationGetDescr(frel)->attrs[index];
+
+			if (attr->attisdropped)
+				continue;
+
+			fmstate->p_types[fmstate->p_nums] = attr->atttypid;
+			getTypeOutputInfo(attr->atttypid, &out_func_oid, &isvarlena);
+			fmgr_info(out_func_oid, &fmstate->p_flinfo[fmstate->p_nums]);
+			fmstate->p_nums++;
+		}
+	}
+	Assert(fmstate->p_nums <= n_params);
+	fmstate->es_query_cxt = mtstate->ps.state->es_query_cxt;
+	resultRelInfo->ri_fdw_state = fmstate;
+}
+
+static void
+prepare_foreign_modify(PostgresFdwModifyState *fmstate)
+{
+	static int	prep_id = 1;
+	char		prep_name[NAMEDATALEN];
+	PGresult   *res;
+
+	snprintf(prep_name, sizeof(prep_name),
+			 "pgsql_fdw_prep_%08x", prep_id++);
+
+	res = PQprepare(fmstate->conn,
+					prep_name,
+					fmstate->query,
+					fmstate->p_nums,
+					fmstate->p_types);
+	if (!res || PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		PQclear(res);
+		elog(ERROR, "could not prepare statement (%s): %s",
+			 fmstate->query, PQerrorMessage(fmstate->conn));
+	}
+	PQclear(res);
+
+	fmstate->p_name = MemoryContextStrdup(fmstate->es_query_cxt, prep_name);
+}
+
+static int
+setup_exec_prepared(ResultRelInfo *resultRelInfo,
+					const char *rowid, HeapTuple tuple,
+					const char *p_values[], int p_lengths[])
+{
+	PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state;
+	Relation	frel = resultRelInfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(frel);
+	int			i, p_idx = 0;
+
+	/* 1st parameter should be ctid */
+	if (rowid)
+	{
+		p_values[p_idx] = rowid;
+		p_lengths[p_idx] = strlen(rowid) + 1;
+		p_idx++;
+	}
+
+	/* following parameters are as TupleDesc */
+	if (HeapTupleIsValid(tuple))
+	{
+		for (i = 0; i < tupdesc->natts; i++)
+		{
+			Form_pg_attribute	attr = tupdesc->attrs[i];
+			Datum		value;
+			bool		isnull;
+
+			if (attr->attisdropped)
+				continue;
+
+			value = heap_getattr(tuple, attr->attnum, tupdesc, &isnull);
+			if (isnull)
+			{
+				p_values[p_idx] = NULL;
+				p_lengths[p_idx] = 0;
+			}
+			else
+			{
+				p_values[p_idx] =
+					OutputFunctionCall(&fmstate->p_flinfo[p_idx], value);
+				p_lengths[p_idx] = strlen(p_values[p_idx]) + 1;
+			}
+			p_idx++;
+		}
+	}
+	return p_idx;
+}
+
+static int
+postgresExecForeignInsert(ResultRelInfo *resultRelInfo,
+						  HeapTuple tuple)
+{
+	PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state;
+	const char	  **p_values  = alloca(sizeof(char *) * fmstate->p_nums);
+	int			   *p_lengths = alloca(sizeof(int) * fmstate->p_nums);
+	AttrNumber		nattrs;
+	PGresult	   *res;
+	int				n_rows;
+
+	if (!fmstate->p_name)
+		prepare_foreign_modify(fmstate);
+
+	nattrs = setup_exec_prepared(resultRelInfo,
+								 NULL, tuple,
+								 p_values, p_lengths);
+	Assert(fmstate->p_nums == nattrs);
+
+	res = PQexecPrepared(fmstate->conn,
+						 fmstate->p_name,
+						 nattrs,
+						 p_values,
+						 p_lengths,
+						 NULL, 0);
+	if (!res ||  PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		PQclear(res);
+		elog(ERROR, "could not execute prepared statement (%s): %s",
+			 fmstate->query, PQerrorMessage(fmstate->conn));
+    }
+	n_rows = atoi(PQcmdTuples(res));
+    PQclear(res);
+
+	return n_rows;
+}
+
+static int
+postgresExecForeignDelete(ResultRelInfo *resultRelInfo, const char *rowid)
+{
+	PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state;
+	const char	   *p_values[1];
+	int				p_lengths[1];
+	AttrNumber		nattrs;
+	PGresult	   *res;
+	int				n_rows;
+
+	if (!fmstate->p_name)
+		prepare_foreign_modify(fmstate);
+
+	nattrs = setup_exec_prepared(resultRelInfo,
+								 rowid, NULL,
+								 p_values, p_lengths);
+	Assert(fmstate->p_nums == nattrs);
+
+	res = PQexecPrepared(fmstate->conn,
+						 fmstate->p_name,
+						 nattrs,
+						 p_values,
+						 p_lengths,
+						 NULL, 0);
+	if (!res ||  PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		PQclear(res);
+		elog(ERROR, "could not execute prepared statement (%s): %s",
+			 fmstate->query, PQerrorMessage(fmstate->conn));
+    }
+	n_rows = atoi(PQcmdTuples(res));
+    PQclear(res);
+
+	return n_rows;
+}
+
+static int
+postgresExecForeignUpdate(ResultRelInfo *resultRelInfo,
+						  const char *rowid, HeapTuple tuple)
+{
+	PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state;
+	const char	  **p_values  = alloca(sizeof(char *) * (fmstate->p_nums + 1));
+	int			   *p_lengths = alloca(sizeof(int) * (fmstate->p_nums + 1));
+	AttrNumber		nattrs;
+	PGresult	   *res;
+	int				n_rows;
+
+	if (!fmstate->p_name)
+		prepare_foreign_modify(fmstate);
+
+	nattrs = setup_exec_prepared(resultRelInfo,
+								 rowid, tuple,
+								 p_values, p_lengths);
+	Assert(fmstate->p_nums == nattrs);
+
+	res = PQexecPrepared(fmstate->conn,
+						 fmstate->p_name,
+						 nattrs,
+						 p_values,
+						 p_lengths,
+						 NULL, 0);
+	if (!res ||  PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		PQclear(res);
+		elog(ERROR, "could not execute prepared statement (%s): %s",
+			 fmstate->query, PQerrorMessage(fmstate->conn));
+    }
+	n_rows = atoi(PQcmdTuples(res));
+    PQclear(res);
+
+	return n_rows;
+}
+
+static void
+postgresEndForeignModify(ResultRelInfo *resultRelInfo)
+{
+	PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state;
+
+	ReleaseConnection(fmstate->conn, false);
+	fmstate->conn = NULL;
+}
diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h
index b5cefb8..3756a0d 100644
--- a/contrib/postgres_fdw/postgres_fdw.h
+++ b/contrib/postgres_fdw/postgres_fdw.h
@@ -30,7 +30,8 @@ int GetFetchCountOption(ForeignTable *table, ForeignServer *server);
 void deparseSimpleSql(StringInfo buf,
 					  PlannerInfo *root,
 					  RelOptInfo *baserel,
-					  List *local_conds);
+					  List *local_conds,
+					  AttrNumber anum_rowid);
 void appendWhereClause(StringInfo buf,
 					   bool has_where,
 					   List *exprs,
@@ -41,5 +42,8 @@ void classifyConditions(PlannerInfo *root,
 						List **param_conds,
 						List **local_conds);
 void deparseAnalyzeSql(StringInfo buf, Relation rel);
+void deparseInsertSql(StringInfo buf, PlannerInfo *root, Index rtindex);
+void deparseUpdateSql(StringInfo buf, PlannerInfo *root, Index rtindex);
+void deparseDeleteSql(StringInfo buf, PlannerInfo *root, Index rtindex);
 
 #endif /* POSTGRESQL_FDW_H */
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 7845e70..30fcef6 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -300,6 +300,22 @@ ERROR OUT;          -- ERROR
 SELECT srvname FROM postgres_fdw_connections;
 
 -- ===================================================================
+-- test for writable foreign table stuff (PoC stage now)
+-- ===================================================================
+EXPLAIN(verbose) 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) 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) 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;
+
+-- ===================================================================
 -- cleanup
 -- ===================================================================
 DROP OPERATOR === (int, int) CASCADE;
pgsql-v9.3-writable-fdw-poc.v8.part-1.patchapplication/octet-stream; name=pgsql-v9.3-writable-fdw-poc.v8.part-1.patchDownload
 doc/src/sgml/ddl.sgml                   |    5 -
 doc/src/sgml/fdwhandler.sgml            |  161 ++++++++++++++++++++++++++++++-
 src/backend/executor/execMain.c         |   34 ++++++-
 src/backend/executor/nodeForeignscan.c  |  136 +++++++++++++++++++++++++-
 src/backend/executor/nodeModifyTable.c  |  153 ++++++++++++++++++++++++-----
 src/backend/foreign/foreign.c           |   68 +++++++++++++
 src/backend/nodes/copyfuncs.c           |    2 +
 src/backend/nodes/outfuncs.c            |    2 +
 src/backend/optimizer/plan/createplan.c |   48 ++++++++-
 src/backend/optimizer/plan/initsplan.c  |   10 +-
 src/backend/optimizer/plan/planmain.c   |    5 +-
 src/backend/optimizer/plan/planner.c    |    8 +-
 src/backend/optimizer/prep/prepunion.c  |    3 +-
 src/backend/optimizer/util/plancat.c    |   27 +++++-
 src/backend/optimizer/util/relnode.c    |    7 +-
 src/backend/parser/parse_relation.c     |   15 ++-
 src/backend/rewrite/rewriteHandler.c    |   55 +++++++++--
 src/include/foreign/fdwapi.h            |   31 ++++++
 src/include/foreign/foreign.h           |    6 ++
 src/include/nodes/execnodes.h           |   13 ++-
 src/include/nodes/plannodes.h           |    2 +
 src/include/optimizer/pathnode.h        |    2 +-
 src/include/optimizer/plancat.h         |    2 +-
 src/include/optimizer/planmain.h        |    6 +-
 24 files changed, 733 insertions(+), 68 deletions(-)

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index d6e5d64..30f9c6b 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -3066,11 +3066,6 @@ ANALYZE measurement;
     <firstterm>user mapping</>, which can provide additional options 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.
-   </para>
  </sect1>
 
  <sect1 id="ddl-others">
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index 2d604ed..4d199e3 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -89,6 +89,54 @@
 
     <para>
 <programlisting>
+AttrNumber
+GetForeignRelWidth(PlannerInfo *root,
+                   RelOptInfo *baserel,
+                   Relation foreignrel,
+                   bool inhparent,
+                   List *targetList);
+</programlisting>
+     Obtain the width of the result set to be fetched during a foreign table scan.
+     This is an optional handler, and called before <literal>GetForeignRelSize</>
+     for a query involving a foreign table
+     (during the construction of <literal>RelOptInfo</>).
+     <literal>root</> is the planner's global information about the query,
+     <literal>baserel</> is the planner's information being constructed for
+     this query, and <literal>foreignrel</> is a <literal>Relation</>
+     descriptor of the foreign table.
+     <literal>inhparent</> is a boolean to show whether the relation is
+     an inheritance parent, even though foreign tables do not support table
+     inheritance right now. <literal>targetList</> is the list of
+     <literal>TargetEntry</> to be returned from the (sub-)query
+     that is currently in focus.
+    </para>
+
+    <para>
+     The result value of this function will be assigned to
+     <literal>baserel-&gt;max_attr</>, that means it is the expected number
+     of columns being fetched during the foreign table scan.
+     It should not be smaller than the number of regular columns in the definition
+     of this foreign table.  You can only return a number greater than his value to
+     acquire slots for some additional attributes, which are called
+     <firstterm>pseudo-columns</>.
+     A typical usage of a pseudo-column is to carry an identifier of
+     a particular remote row to be updated or deleted from the scanning stage
+     to the modifying stage when the foreign table is the target of
+     a data-modifying SQL statement.
+     You can return the result of the helper function
+     <literal>get_pseudo_rowid_column</> if this <literal>"rowid"</>
+     pseudo-column is the only one you need.
+   </para>
+
+   <para>
+     In addition to that, pseudo-columns can be used to off-load the burden of
+     complex calculations to foreign computing resources by replacing an
+     expression with a reference to its result, which is calculated on the
+     remote side rather than locally.
+   </para>
+
+    <para>
+<programlisting>
 void
 GetForeignRelSize (PlannerInfo *root,
                    RelOptInfo *baserel,
@@ -96,7 +144,8 @@ 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.
+     for a query involving a foreign table at the beginning of planning
+     or right after <literal>GetForeignRelWidth</>, if that callback is configured.
      <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
@@ -315,6 +364,116 @@ AcquireSampleRowsFunc (Relation relation, int elevel,
     </para>
 
     <para>
+     If a FDW supports writable foreign tables, it must implement at least both
+     <literal>BeginForeignModify</> and <literal>EndForeignModify</>.
+     In addition, some or all of <literal>ExecForeignInsert</>,
+     <literal>ExecForeignUpdate</> and <literal>ExecForeignDelete</> need
+     to be implement according to the capability of the FDW.
+    </para>     
+
+    <para>
+<programlisting>
+List *
+PlanForeignModify(PlannerInfo *root,
+                  ModifyTable *plan,
+                  Index resultRelation,
+                  Plan *subplan);
+</programlisting>
+     It allows FDW drivers to construct private information relevant to
+     the modification of the foreign table.  This private information must have
+     the form of a <literal>List *</>, which will be delivered as
+     third argument to <literal>BeginForeignModify</> during the execution stage.
+    </para>
+
+    <para>
+     <literal>root</> is the planner's global information about the query.
+     <literal>plan</> is the master plan to modify the result relation according
+     to its command type.  Please consider that <literal>ModifyTable</>
+     may have multiple result relations in a future revision, even though
+     currently there is no support for table inheritance on foreign tables.
+     <literal>resultRelation</> is an index for the result relation in the
+     range table entries, and <literal>subplan</> is the relevant scan plan;
+     that should be a <literal>ForeignScan</> for <literal>UPDATE</> or
+     <literal>DELETE</>, so the driver can access the private information of
+     the scan stage using this argument.
+    </para>
+
+    <para>
+<programlisting>
+void
+BeginForeignModify (ModifyTableState *mtstate,
+                    ResultRelInfo *resultRelInfo,
+                    List *fdw_private,
+                    Plan *subplan,
+                    int eflags);
+</programlisting>
+     It is invoked at beginning of foreign table modification, during
+     executor startup.  This routine should perform any initialization
+     needed prior to the actual table modifications, but not start
+     modifying the actual tuples. (That should be done during each call of
+     <function>ExecForeignInsert</>, <function>ExecForeignUpdate</> or
+     <function>ExecForeignDelete</>.)
+    </para>
+
+    <para>
+     The <structfield>ri_fdw_state</> field of <structname>ResultRelInfo</>
+     is reserved to store any private information relevant to this foreign table,
+     so it should be initialized to contain the per-scan state.
+     Please consider that <literal>ModifyTableState</> may have multiple
+     result relations in a future revision, even though currently there is no
+     support for table inheritance on foreign tables.  <literal>resultRelInfo</>
+     is the master information connected to this foreign table.
+     <literal>fdw_private</> is private information constructed in
+     <literal>PlanForeignModify</>, and <literal>subplan</> is the relevant
+     scan plan of this table modification.
+    </para>
+
+    <para>
+<programlisting>
+int
+ExecForeignInsert (ResultRelInfo *resultRelInfo,
+                   HeapTuple tuple);
+</programlisting>
+     Insert the given tuple into backing storage on behalf of the foreign table.
+     The supplied tuple is already formed according to the definition of
+     the relation, thus all the pseudo-columns are already filtered out.
+     It can return the number of rows being inserted, usually 1.
+    </para>
+
+    <para>
+<programlisting>
+int
+ExecForeignDelete (ResultRelInfo *resultRelInfo,
+                   const char *rowid);
+</programlisting>
+     Delete the tuple being identified with <literal>rowid</> from the backing
+     storage on behalf of the foreign table.
+     It can return the number of rows being deleted, usually 1.
+    </para>
+
+    <para>
+<programlisting>
+int
+ExecForeignUpdate (ResultRelInfo *resultRelInfo,
+                   const char *rowid,
+                   HeapTuple tuple);
+</programlisting>
+     Update the tuple being identified with <literal>rowid</> in the backing
+     storage on behalf of the foreign table with the given tuple.
+     It can return the number of rows being updated, usually 1.
+    </para>
+
+    <para>
+<programlisting>
+void
+EndForeignModify (ResultRelInfo *resultRelInfo);
+</programlisting>
+     End the modification 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>
      The <structname>FdwRoutine</> struct type is declared in
      <filename>src/include/foreign/fdwapi.h</>, which see for additional
      details.
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 0222d40..60f4c94 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"
@@ -933,6 +934,7 @@ void
 CheckValidResultRel(Relation resultRel, CmdType operation)
 {
 	TriggerDesc *trigDesc = resultRel->trigdesc;
+	FdwRoutine	*fdwroutine;
 
 	switch (resultRel->rd_rel->relkind)
 	{
@@ -991,10 +993,34 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
 			}
 			break;
 		case RELKIND_FOREIGN_TABLE:
-			ereport(ERROR,
-					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("cannot change foreign table \"%s\"",
-							RelationGetRelationName(resultRel))));
+			fdwroutine = GetFdwRoutineByRelId(RelationGetRelid(resultRel));
+			switch (operation)
+			{
+				case CMD_INSERT:
+					if (!fdwroutine || !fdwroutine->ExecForeignInsert)
+						ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("cannot insert into foreign table \"%s\"",
+									RelationGetRelationName(resultRel))));
+					break;
+				case CMD_UPDATE:
+					if (!fdwroutine || !fdwroutine->ExecForeignUpdate)
+						ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("cannot update foreign table \"%s\"",
+									RelationGetRelationName(resultRel))));
+					break;
+				case CMD_DELETE:
+					if (!fdwroutine || !fdwroutine->ExecForeignDelete)
+						ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("cannot delete from foreign table \"%s\"",
+									RelationGetRelationName(resultRel))));
+					break;
+				default:
+					elog(ERROR, "unrecognized CmdType: %d", (int) operation);
+					break;
+			}
 			break;
 		default:
 			ereport(ERROR,
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 9204859..1ee626f 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -25,6 +25,7 @@
 #include "executor/executor.h"
 #include "executor/nodeForeignscan.h"
 #include "foreign/fdwapi.h"
+#include "nodes/nodeFuncs.h"
 #include "utils/rel.h"
 
 static TupleTableSlot *ForeignNext(ForeignScanState *node);
@@ -93,6 +94,133 @@ ExecForeignScan(ForeignScanState *node)
 					(ExecScanRecheckMtd) ForeignRecheck);
 }
 
+/*
+ * pseudo_column_walker
+ *
+ * helper routine of GetPseudoTupleDesc. It pulls Var nodes that reference
+ * pseudo columns from targetlis of the relation
+ */
+typedef struct
+{
+	Relation	relation;
+	Index		varno;
+	List	   *pcolumns;
+	AttrNumber	max_attno;
+} pseudo_column_walker_context;
+
+static bool
+pseudo_column_walker(Node *node, pseudo_column_walker_context *context)
+{
+	if (node == NULL)
+		return false;
+	if (IsA(node, Var))
+	{
+		Var		   *var = (Var *) node;
+		ListCell   *cell;
+
+		if (var->varno == context->varno && var->varlevelsup == 0 &&
+			var->varattno > RelationGetNumberOfAttributes(context->relation))
+		{
+			foreach (cell, context->pcolumns)
+			{
+				Var	   *temp = lfirst(cell);
+
+				if (temp->varattno == var->varattno)
+				{
+					if (!equal(var, temp))
+						elog(ERROR, "asymmetric pseudo column appeared");
+					break;
+				}
+			}
+			if (!cell)
+			{
+				context->pcolumns = lappend(context->pcolumns, var);
+				if (var->varattno > context->max_attno)
+					context->max_attno = var->varattno;
+			}
+		}
+		return false;
+	}
+
+	/* Should not find an unplanned subquery */
+	Assert(!IsA(node, Query));
+
+	return expression_tree_walker(node, pseudo_column_walker,
+								  (void *)context);
+}
+
+/*
+ * GetPseudoTupleDesc
+ *
+ * It generates TupleDesc structure including pseudo-columns if required.
+ */
+static TupleDesc
+GetPseudoTupleDesc(ForeignScan *node, Relation relation)
+{
+	pseudo_column_walker_context context;
+	List	   *target_list = node->scan.plan.targetlist;
+	TupleDesc	tupdesc;
+	AttrNumber	attno;
+	ListCell   *cell;
+	ListCell   *prev;
+	bool		hasoid;
+
+	context.relation = relation;
+	context.varno = node->scan.scanrelid;
+	context.pcolumns = NIL;
+	context.max_attno = -1;
+
+	pseudo_column_walker((Node *)target_list, (void *)&context);
+	Assert(context.max_attno > RelationGetNumberOfAttributes(relation));
+
+	hasoid = RelationGetForm(relation)->relhasoids;
+	tupdesc = CreateTemplateTupleDesc(context.max_attno, hasoid);
+
+	for (attno = 1; attno <= context.max_attno; attno++)
+	{
+		/* case of regular columns */
+		if (attno <= RelationGetNumberOfAttributes(relation))
+		{
+			memcpy(tupdesc->attrs[attno - 1],
+				   RelationGetDescr(relation)->attrs[attno - 1],
+				   ATTRIBUTE_FIXED_PART_SIZE);
+			continue;
+		}
+
+		/* case of pseudo columns */
+		prev = NULL;
+		foreach (cell, context.pcolumns)
+		{
+			Var	   *var = lfirst(cell);
+
+			if (var->varattno == attno)
+			{
+				char		namebuf[NAMEDATALEN];
+
+				snprintf(namebuf, sizeof(namebuf),
+						 "pseudo_column_%d", attno);
+
+				TupleDescInitEntry(tupdesc,
+								   attno,
+								   namebuf,
+								   var->vartype,
+								   var->vartypmod,
+								   0);
+				TupleDescInitEntryCollation(tupdesc,
+											attno,
+											var->varcollid);
+				context.pcolumns
+					= list_delete_cell(context.pcolumns, cell, prev);
+				break;
+			}
+			prev = cell;
+		}
+		if (!cell)
+			elog(ERROR, "pseudo column %d of %s not in target list",
+				 attno, RelationGetRelationName(relation));
+	}
+	return tupdesc;
+}
 
 /* ----------------------------------------------------------------
  *		ExecInitForeignScan
@@ -103,6 +231,7 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
 {
 	ForeignScanState *scanstate;
 	Relation	currentRelation;
+	TupleDesc	tupdesc;
 	FdwRoutine *fdwroutine;
 
 	/* check for unsupported flags */
@@ -149,7 +278,12 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
 	/*
 	 * get the scan type from the relation descriptor.
 	 */
-	ExecAssignScanType(&scanstate->ss, RelationGetDescr(currentRelation));
+	if (node->fsPseudoCol)
+		tupdesc = GetPseudoTupleDesc(node, currentRelation);
+	else
+		tupdesc = RelationGetDescr(currentRelation);
+
+	ExecAssignScanType(&scanstate->ss, tupdesc);
 
 	/*
 	 * Initialize result tuple type and projection info.
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d31015c..6c2b6e5 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"
@@ -170,6 +171,7 @@ ExecInsert(TupleTableSlot *slot,
 	Relation	resultRelationDesc;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
+	int			num_rows = 1;
 
 	/*
 	 * get the heap tuple out of the tuple table slot, making sure we have a
@@ -225,6 +227,14 @@ ExecInsert(TupleTableSlot *slot,
 
 		newId = InvalidOid;
 	}
+	else if (resultRelInfo->ri_fdwroutine)
+	{
+		FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+		num_rows = fdwroutine->ExecForeignInsert(resultRelInfo, tuple);
+
+		newId = InvalidOid;
+	}
 	else
 	{
 		/*
@@ -252,7 +262,7 @@ ExecInsert(TupleTableSlot *slot,
 
 	if (canSetTag)
 	{
-		(estate->es_processed)++;
+		(estate->es_processed) += num_rows;
 		estate->es_lastoid = newId;
 		setLastTid(&(tuple->t_self));
 	}
@@ -285,7 +295,7 @@ ExecInsert(TupleTableSlot *slot,
  * ----------------------------------------------------------------
  */
 static TupleTableSlot *
-ExecDelete(ItemPointer tupleid,
+ExecDelete(Datum rowid,
 		   HeapTupleHeader oldtuple,
 		   TupleTableSlot *planSlot,
 		   EPQState *epqstate,
@@ -296,6 +306,7 @@ ExecDelete(ItemPointer tupleid,
 	Relation	resultRelationDesc;
 	HTSU_Result result;
 	HeapUpdateFailureData hufd;
+	int			num_rows = 1;
 
 	/*
 	 * get information on the (current) result relation
@@ -310,7 +321,7 @@ ExecDelete(ItemPointer tupleid,
 		bool		dodelete;
 
 		dodelete = ExecBRDeleteTriggers(estate, epqstate, resultRelInfo,
-										tupleid);
+										(ItemPointer)DatumGetPointer(rowid));
 
 		if (!dodelete)			/* "do nothing" */
 			return NULL;
@@ -334,8 +345,17 @@ ExecDelete(ItemPointer tupleid,
 		if (!dodelete)			/* "do nothing" */
 			return NULL;
 	}
+	else if (resultRelInfo->ri_fdwroutine)
+	{
+		FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+		num_rows = fdwroutine->ExecForeignDelete(resultRelInfo,
+												 DatumGetCString(rowid));
+	}
 	else
 	{
+		ItemPointer	tupleid = (ItemPointer) DatumGetPointer(rowid);
+
 		/*
 		 * delete the tuple
 		 *
@@ -430,10 +450,11 @@ ldelete:;
 	}
 
 	if (canSetTag)
-		(estate->es_processed)++;
+		(estate->es_processed) += num_rows;
 
 	/* AFTER ROW DELETE Triggers */
-	ExecARDeleteTriggers(estate, resultRelInfo, tupleid);
+	ExecARDeleteTriggers(estate, resultRelInfo,
+						 (ItemPointer)DatumGetPointer(rowid));
 
 	/* Process RETURNING if present */
 	if (resultRelInfo->ri_projectReturning)
@@ -457,7 +478,8 @@ ldelete:;
 		}
 		else
 		{
-			deltuple.t_self = *tupleid;
+			ItemPointerCopy((ItemPointer)DatumGetPointer(rowid),
+							&deltuple.t_self);
 			if (!heap_fetch(resultRelationDesc, SnapshotAny,
 							&deltuple, &delbuffer, false, NULL))
 				elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
@@ -499,7 +521,7 @@ ldelete:;
  * ----------------------------------------------------------------
  */
 static TupleTableSlot *
-ExecUpdate(ItemPointer tupleid,
+ExecUpdate(Datum rowid,
 		   HeapTupleHeader oldtuple,
 		   TupleTableSlot *slot,
 		   TupleTableSlot *planSlot,
@@ -513,6 +535,7 @@ ExecUpdate(ItemPointer tupleid,
 	HTSU_Result result;
 	HeapUpdateFailureData hufd;
 	List	   *recheckIndexes = NIL;
+	int			num_rows = 1;
 
 	/*
 	 * abort the operation if not running transactions
@@ -537,7 +560,7 @@ ExecUpdate(ItemPointer tupleid,
 		resultRelInfo->ri_TrigDesc->trig_update_before_row)
 	{
 		slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
-									tupleid, slot);
+									(ItemPointer)DatumGetPointer(rowid), slot);
 
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
@@ -567,8 +590,18 @@ ExecUpdate(ItemPointer tupleid,
 		/* trigger might have changed tuple */
 		tuple = ExecMaterializeSlot(slot);
 	}
+	else if (resultRelInfo->ri_fdwroutine)
+	{
+		FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+		num_rows = fdwroutine->ExecForeignUpdate(resultRelInfo,
+												 DatumGetCString(rowid),
+												 tuple);
+	}
 	else
 	{
+		ItemPointer	tupleid = (ItemPointer) DatumGetPointer(rowid);
+
 		/*
 		 * Check the constraints of the tuple
 		 *
@@ -687,11 +720,12 @@ lreplace:;
 	}
 
 	if (canSetTag)
-		(estate->es_processed)++;
+		(estate->es_processed) += num_rows;
 
 	/* AFTER ROW UPDATE Triggers */
-	ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple,
-						 recheckIndexes);
+	ExecARUpdateTriggers(estate, resultRelInfo,
+						 (ItemPointer) DatumGetPointer(rowid),
+						 tuple, recheckIndexes);
 
 	list_free(recheckIndexes);
 
@@ -771,6 +805,7 @@ ExecModifyTable(ModifyTableState *node)
 	TupleTableSlot *planSlot;
 	ItemPointer tupleid = NULL;
 	ItemPointerData tuple_ctid;
+	Datum		rowid = 0;
 	HeapTupleHeader oldtuple = NULL;
 
 	/*
@@ -859,17 +894,19 @@ ExecModifyTable(ModifyTableState *node)
 		if (junkfilter != NULL)
 		{
 			/*
-			 * extract the 'ctid' or 'wholerow' junk attribute.
+			 * extract the 'ctid', 'rowid' or 'wholerow' junk attribute.
 			 */
 			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,
+												 junkfilter->jf_junkRowidNo,
 												 &isNull);
 					/* shouldn't ever get a null result... */
 					if (isNull)
@@ -877,13 +914,33 @@ ExecModifyTable(ModifyTableState *node)
 
 					tupleid = (ItemPointer) DatumGetPointer(datum);
 					tuple_ctid = *tupleid;		/* be sure we don't free
-												 * ctid!! */
-					tupleid = &tuple_ctid;
+												 * ctid ! */
+					rowid = PointerGetDatum(&tuple_ctid);
+				}
+				else if (relkind == RELKIND_FOREIGN_TABLE)
+				{
+					datum = ExecGetJunkAttribute(slot,
+												 junkfilter->jf_junkRowidNo,
+												 &isNull);
+					/* shouldn't ever get a null result... */
+					if (isNull)
+						elog(ERROR, "rowid is NULL");
+
+					rowid = datum;
+
+					datum = ExecGetJunkAttribute(slot,
+												 junkfilter->jf_junkRecordNo,
+												 &isNull);
+					/* shouldn't ever get a null result... */
+					if (isNull)
+						elog(ERROR, "wholerow is NULL");
+
+					oldtuple = DatumGetHeapTupleHeader(datum);
 				}
 				else
 				{
 					datum = ExecGetJunkAttribute(slot,
-												 junkfilter->jf_junkAttNo,
+												 junkfilter->jf_junkRecordNo,
 												 &isNull);
 					/* shouldn't ever get a null result... */
 					if (isNull)
@@ -906,11 +963,11 @@ ExecModifyTable(ModifyTableState *node)
 				slot = ExecInsert(slot, planSlot, estate, node->canSetTag);
 				break;
 			case CMD_UPDATE:
-				slot = ExecUpdate(tupleid, oldtuple, slot, planSlot,
+				slot = ExecUpdate(rowid, oldtuple, slot, planSlot,
 								&node->mt_epqstate, estate, node->canSetTag);
 				break;
 			case CMD_DELETE:
-				slot = ExecDelete(tupleid, oldtuple, planSlot,
+				slot = ExecDelete(rowid, oldtuple, planSlot,
 								&node->mt_epqstate, estate, node->canSetTag);
 				break;
 			default:
@@ -997,6 +1054,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	i = 0;
 	foreach(l, node->plans)
 	{
+		char	relkind;
+
 		subplan = (Plan *) lfirst(l);
 
 		/*
@@ -1022,6 +1081,24 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		estate->es_result_relation_info = resultRelInfo;
 		mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
 
+		/*
+		 * Also tells FDW extensions to init the plan for this result rel
+		 */
+		relkind = RelationGetForm(resultRelInfo->ri_RelationDesc)->relkind;
+		if (relkind == RELKIND_FOREIGN_TABLE)
+		{
+			Oid		relid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+			FdwRoutine *fdwroutine = GetFdwRoutineByRelId(relid);
+			List   *fdwprivate = list_nth(node->fdwPrivList, i);
+
+			Assert(fdwroutine != NULL);
+			resultRelInfo->ri_fdwroutine = fdwroutine;
+			resultRelInfo->ri_fdw_state = NULL;
+
+			if (fdwroutine->BeginForeignModify)
+				fdwroutine->BeginForeignModify(mtstate, resultRelInfo,
+											   fdwprivate, subplan, eflags);
+		}
 		resultRelInfo++;
 		i++;
 	}
@@ -1163,6 +1240,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			for (i = 0; i < nplans; i++)
 			{
 				JunkFilter *j;
+				char		relkind =
+				    RelationGetForm(resultRelInfo->ri_RelationDesc)->relkind;
 
 				subplan = mtstate->mt_plans[i]->plan;
 				if (operation == CMD_INSERT || operation == CMD_UPDATE)
@@ -1176,16 +1255,27 @@ 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)
+					if (relkind == RELKIND_RELATION)
 					{
-						j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
-						if (!AttributeNumberIsValid(j->jf_junkAttNo))
+						j->jf_junkRowidNo = ExecFindJunkAttribute(j, "ctid");
+						if (!AttributeNumberIsValid(j->jf_junkRowidNo))
 							elog(ERROR, "could not find junk ctid column");
 					}
+					else if (relkind == RELKIND_FOREIGN_TABLE)
+					{
+						j->jf_junkRowidNo = ExecFindJunkAttribute(j, "rowid");
+						if (!AttributeNumberIsValid(j->jf_junkRowidNo))
+							elog(ERROR, "could not find junk rowid column");
+						j->jf_junkRecordNo
+							= ExecFindJunkAttribute(j, "record");
+						if (!AttributeNumberIsValid(j->jf_junkRecordNo))
+							elog(ERROR, "could not find junk wholerow column");
+					}
 					else
 					{
-						j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
-						if (!AttributeNumberIsValid(j->jf_junkAttNo))
+						j->jf_junkRecordNo
+							= ExecFindJunkAttribute(j, "wholerow");
+						if (!AttributeNumberIsValid(j->jf_junkRecordNo))
 							elog(ERROR, "could not find junk wholerow column");
 					}
 				}
@@ -1239,6 +1329,21 @@ ExecEndModifyTable(ModifyTableState *node)
 {
 	int			i;
 
+	/* Let the FDW shut dowm */
+	for (i=0; i < node->ps.state->es_num_result_relations; i++)
+	{
+		ResultRelInfo  *resultRelInfo
+			= &node->ps.state->es_result_relations[i];
+
+		if (resultRelInfo->ri_fdwroutine)
+		{
+			FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+			if (fdwroutine->EndForeignModify)
+				fdwroutine->EndForeignModify(resultRelInfo);
+		}
+	}
+
 	/*
 	 * Free the exprcontext
 	 */
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
index d8845aa..4363a28 100644
--- a/src/backend/foreign/foreign.c
+++ b/src/backend/foreign/foreign.c
@@ -571,3 +571,71 @@ get_foreign_server_oid(const char *servername, bool missing_ok)
 				 errmsg("server \"%s\" does not exist", servername)));
 	return oid;
 }
+
+/*
+ * get_pseudo_rowid_column
+ *
+ * It picks up an attribute number to be used for the pseudo rowid column,
+ * if it exists.  It should be injected at rewriteHandler.c if the supplied query
+ * is a UPDATE or DELETE command.  Elsewhere, it returns InvalidAttrNumber.
+ */
+AttrNumber
+get_pseudo_rowid_column(RelOptInfo *baserel, List *targetList)
+{
+	ListCell   *cell;
+
+	foreach (cell, targetList)
+	{
+		TargetEntry *tle = lfirst(cell);
+
+		if (tle->resjunk &&
+			tle->resname && strcmp(tle->resname, "rowid") == 0)
+		{
+			Var	   *var;
+
+			if (!IsA(tle->expr, Var))
+				elog(ERROR, "unexpected node on junk rowid entry: %d",
+					 (int) nodeTag(tle->expr));
+
+			var = (Var *) tle->expr;
+			if (baserel->relid == var->varno)
+				return var->varattno;
+		}
+	}
+	return InvalidAttrNumber;
+}
+
+/*
+ * lookup_foreign_scan_plan
+ *
+ * It looks up the ForeignScan plan node with the supplied range-table id.
+ * Its typical usage is for PlanForeignModify to get the underlying scan plan on
+ * UPDATE or DELETE commands.
+ */
+ForeignScan *
+lookup_foreign_scan_plan(Plan *subplan, Index rtindex)
+{
+	if (!subplan)
+		return NULL;
+
+	else if (IsA(subplan, ForeignScan))
+	{
+		ForeignScan	   *fscan = (ForeignScan *) subplan;
+
+		if (fscan->scan.scanrelid == rtindex)
+			return fscan;
+	}
+	else
+	{
+		ForeignScan    *fscan;
+
+		fscan = lookup_foreign_scan_plan(subplan->lefttree, rtindex);
+		if (fscan != NULL)
+			return fscan;
+
+		fscan = lookup_foreign_scan_plan(subplan->righttree, rtindex);
+		if (fscan != NULL)
+			return fscan;
+	}
+	return NULL;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 9387ee9..5b1e8ac 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -181,6 +181,7 @@ _copyModifyTable(const ModifyTable *from)
 	COPY_NODE_FIELD(returningLists);
 	COPY_NODE_FIELD(rowMarks);
 	COPY_SCALAR_FIELD(epqParam);
+	COPY_NODE_FIELD(fdwPrivList);
 
 	return newnode;
 }
@@ -594,6 +595,7 @@ _copyForeignScan(const ForeignScan *from)
 	COPY_NODE_FIELD(fdw_exprs);
 	COPY_NODE_FIELD(fdw_private);
 	COPY_SCALAR_FIELD(fsSystemCol);
+	COPY_SCALAR_FIELD(fsPseudoCol);
 
 	return newnode;
 }
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 35c6287..c32ebd3 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -335,6 +335,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
 	WRITE_NODE_FIELD(returningLists);
 	WRITE_NODE_FIELD(rowMarks);
 	WRITE_INT_FIELD(epqParam);
+	WRITE_NODE_FIELD(fdwPrivList);
 }
 
 static void
@@ -562,6 +563,7 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
 	WRITE_NODE_FIELD(fdw_exprs);
 	WRITE_NODE_FIELD(fdw_private);
 	WRITE_BOOL_FIELD(fsSystemCol);
+	WRITE_BOOL_FIELD(fsPseudoCol);
 }
 
 static void
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 030f420..91db1b8 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -39,6 +39,7 @@
 #include "parser/parse_clause.h"
 #include "parser/parsetree.h"
 #include "utils/lsyscache.h"
+#include "utils/rel.h"
 
 
 static Plan *create_plan_recurse(PlannerInfo *root, Path *best_path);
@@ -1943,6 +1944,8 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 	RelOptInfo *rel = best_path->path.parent;
 	Index		scan_relid = rel->relid;
 	RangeTblEntry *rte;
+	Relation	relation;
+	AttrNumber	num_attrs;
 	int			i;
 
 	/* it should be a base rel... */
@@ -2001,6 +2004,22 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 		}
 	}
 
+	/*
+	 * Also, detect whether any pseudo columns are requested from rel.
+	 */
+	relation = heap_open(rte->relid, NoLock);
+	scan_plan->fsPseudoCol = false;
+	num_attrs = RelationGetNumberOfAttributes(relation);
+	for (i = num_attrs + 1; i <= rel->max_attr; i++)
+	{
+		if (!bms_is_empty(rel->attr_needed[i - rel->min_attr]))
+		{
+			scan_plan->fsPseudoCol = true;
+			break;
+		}
+	}
+	heap_close(relation, NoLock);
+
 	return scan_plan;
 }
 
@@ -4695,7 +4714,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)
@@ -4704,6 +4724,8 @@ make_modifytable(CmdType operation, bool canSetTag,
 	Plan	   *plan = &node->plan;
 	double		total_size;
 	ListCell   *subnode;
+	ListCell   *resultRel;
+	List	   *fdw_priv_list = NIL;
 
 	Assert(list_length(resultRelations) == list_length(subplans));
 	Assert(returningLists == NIL ||
@@ -4746,6 +4768,30 @@ make_modifytable(CmdType operation, bool canSetTag,
 	node->rowMarks = rowMarks;
 	node->epqParam = epqParam;
 
+	/*
+	 * Allow FDW driver to construct its private plan if the result relation
+	 * is a foreign table.
+	 */
+	forboth (resultRel, resultRelations, subnode, subplans)
+	{
+		RangeTblEntry  *rte = rt_fetch(lfirst_int(resultRel),
+									   root->parse->rtable);
+		List		   *fdw_private = NIL;
+		char			relkind = get_rel_relkind(rte->relid);
+
+		if (relkind == RELKIND_FOREIGN_TABLE)
+		{
+			FdwRoutine *fdwroutine = GetFdwRoutineByRelId(rte->relid);
+
+			if (fdwroutine && fdwroutine->PlanForeignModify)
+				fdw_private = fdwroutine->PlanForeignModify(root, node,
+													lfirst_int(resultRel),
+													(Plan *) lfirst(subnode));
+		}
+		fdw_priv_list = lappend(fdw_priv_list, fdw_private);
+	}
+	node->fdwPrivList = fdw_priv_list;
+
 	return node;
 }
 
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index bd719b5..57ba192 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -85,7 +85,7 @@ static void check_hashjoinable(RestrictInfo *restrictinfo);
  * "other rel" RelOptInfos for the members of any appendrels we find here.)
  */
 void
-add_base_rels_to_query(PlannerInfo *root, Node *jtnode)
+add_base_rels_to_query(PlannerInfo *root, List *tlist, Node *jtnode)
 {
 	if (jtnode == NULL)
 		return;
@@ -93,7 +93,7 @@ add_base_rels_to_query(PlannerInfo *root, Node *jtnode)
 	{
 		int			varno = ((RangeTblRef *) jtnode)->rtindex;
 
-		(void) build_simple_rel(root, varno, RELOPT_BASEREL);
+		(void) build_simple_rel(root, varno, tlist, RELOPT_BASEREL);
 	}
 	else if (IsA(jtnode, FromExpr))
 	{
@@ -101,14 +101,14 @@ add_base_rels_to_query(PlannerInfo *root, Node *jtnode)
 		ListCell   *l;
 
 		foreach(l, f->fromlist)
-			add_base_rels_to_query(root, lfirst(l));
+			add_base_rels_to_query(root, tlist, lfirst(l));
 	}
 	else if (IsA(jtnode, JoinExpr))
 	{
 		JoinExpr   *j = (JoinExpr *) jtnode;
 
-		add_base_rels_to_query(root, j->larg);
-		add_base_rels_to_query(root, j->rarg);
+		add_base_rels_to_query(root, tlist, j->larg);
+		add_base_rels_to_query(root, tlist, j->rarg);
 	}
 	else
 		elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
index c2488a4..4442444 100644
--- a/src/backend/optimizer/plan/planmain.c
+++ b/src/backend/optimizer/plan/planmain.c
@@ -112,6 +112,9 @@ query_planner(PlannerInfo *root, List *tlist,
 	 */
 	if (parse->jointree->fromlist == NIL)
 	{
+		/* Make a flattened version of the rangetable for faster access */
+		setup_simple_rel_arrays(root);
+
 		/* We need a trivial path result */
 		*cheapest_path = (Path *)
 			create_result_path((List *) parse->jointree->quals);
@@ -163,7 +166,7 @@ query_planner(PlannerInfo *root, List *tlist,
 	 * rangetable may contain RTEs for rels not actively part of the query,
 	 * for example views.  We don't want to make RelOptInfos for them.
 	 */
-	add_base_rels_to_query(root, (Node *) parse->jointree);
+	add_base_rels_to_query(root, tlist, (Node *) parse->jointree);
 
 	/*
 	 * Examine the targetlist and join tree, adding entries to baserel
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b61005f..2d72244 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,
@@ -3385,7 +3387,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 	setup_simple_rel_arrays(root);
 
 	/* Build RelOptInfo */
-	rel = build_simple_rel(root, 1, RELOPT_BASEREL);
+	rel = build_simple_rel(root, 1, NIL, RELOPT_BASEREL);
 
 	/* Locate IndexOptInfo for the target index */
 	indexInfo = NULL;
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index b91e9f4..61bc447 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -236,7 +236,8 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 		 * used for anything here, but it carries the subroot data structures
 		 * forward to setrefs.c processing.
 		 */
-		rel = build_simple_rel(root, rtr->rtindex, RELOPT_BASEREL);
+		rel = build_simple_rel(root, rtr->rtindex, refnames_tlist,
+							   RELOPT_BASEREL);
 
 		/* plan_params should not be in use in current query level */
 		Assert(root->plan_params == NIL);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 04d5028..7d6bc4b 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -25,6 +25,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/heap.h"
+#include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/clauses.h"
@@ -80,7 +81,7 @@ static List *build_index_tlist(PlannerInfo *root, IndexOptInfo *index,
  */
 void
 get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
-				  RelOptInfo *rel)
+				  List *tlist, RelOptInfo *rel)
 {
 	Index		varno = rel->relid;
 	Relation	relation;
@@ -104,6 +105,30 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 	rel->max_attr = RelationGetNumberOfAttributes(relation);
 	rel->reltablespace = RelationGetForm(relation)->reltablespace;
 
+	/*
+	 * Adjust width of attr_needed slot in case FDW extension wants
+	 * to return pseudo-columns in addition to the columns in its
+	 * table definition.
+	 * GetForeignRelWidth, an optional FDW handler, enables a FDW
+	 * to save properties of a pseudo-column in its private field.
+	 * When the foreign table is the target of UPDATE/DELETE, the query rewriter
+	 * injects a "rowid" pseudo-column to track the remote row to be modified,
+	 * so the FDW has to track which varattno shall perform as "rowid".
+	 */
+	if (RelationGetForm(relation)->relkind == RELKIND_FOREIGN_TABLE)
+	{
+		FdwRoutine *fdwroutine = GetFdwRoutineByRelId(relationObjectId);
+
+		if (fdwroutine->GetForeignRelWidth)
+		{
+			rel->max_attr = fdwroutine->GetForeignRelWidth(root, rel,
+														   relation,
+														   inhparent,
+														   tlist);
+			Assert(rel->max_attr >= RelationGetNumberOfAttributes(relation));
+		}
+	}
+
 	Assert(rel->max_attr >= rel->min_attr);
 	rel->attr_needed = (Relids *)
 		palloc0((rel->max_attr - rel->min_attr + 1) * sizeof(Relids));
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index f724714..84b674c 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -80,7 +80,8 @@ setup_simple_rel_arrays(PlannerInfo *root)
  *	  Construct a new RelOptInfo for a base relation or 'other' relation.
  */
 RelOptInfo *
-build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
+build_simple_rel(PlannerInfo *root, int relid, List *tlist,
+				 RelOptKind reloptkind)
 {
 	RelOptInfo *rel;
 	RangeTblEntry *rte;
@@ -133,7 +134,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
 	{
 		case RTE_RELATION:
 			/* Table --- retrieve statistics from the system catalogs */
-			get_relation_info(root, rte->relid, rte->inh, rel);
+			get_relation_info(root, rte->relid, rte->inh, tlist, rel);
 			break;
 		case RTE_SUBQUERY:
 		case RTE_FUNCTION:
@@ -180,7 +181,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
 			if (appinfo->parent_relid != relid)
 				continue;
 
-			(void) build_simple_rel(root, appinfo->child_relid,
+			(void) build_simple_rel(root, appinfo->child_relid, tlist,
 									RELOPT_OTHER_MEMBER_REL);
 		}
 	}
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 36d95eb..7d4bb99 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -2087,7 +2087,20 @@ get_rte_attribute_name(RangeTblEntry *rte, AttrNumber attnum)
 	 * built (which can easily happen for rules).
 	 */
 	if (rte->rtekind == RTE_RELATION)
-		return get_relid_attribute_name(rte->relid, attnum);
+	{
+		char   *attname = get_attname(rte->relid, attnum);
+
+		if (attname)
+			return attname;
+
+		/*
+		 * XXX - If FDW driver adds pseudo-columns, it may have attribute
+		 * number larger than number of relation's attribute. In this case,
+		 * get_attname() returns NULL and we fall back on alias list on eref.
+		 * It should not happen other than foreign tables.
+		 */
+		Assert(get_rel_relkind(rte->relid) == RELKIND_FOREIGN_TABLE);
+	}
 
 	/*
 	 * Otherwise use the column name from eref.  There should always be one.
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 990ca34..2aa46ee 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1165,7 +1165,10 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 					Relation target_relation)
 {
 	Var		   *var;
-	const char *attrname;
+	List	   *varList;
+	List	   *attNameList;
+	ListCell   *cell1;
+	ListCell   *cell2;
 	TargetEntry *tle;
 
 	if (target_relation->rd_rel->relkind == RELKIND_RELATION)
@@ -1179,8 +1182,35 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 					  -1,
 					  InvalidOid,
 					  0);
+		varList = list_make1(var);
+		attNameList = list_make1("ctid");
+	}
+	else if (target_relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+	{
+		/*
+		 * Emit Rowid so that executor can find the row to update or delete.
+		 */
+		var = makeVar(parsetree->resultRelation,
+					  RelationGetNumberOfAttributes(target_relation) + 1,
+					  CSTRINGOID,
+					  -2,
+					  InvalidOid,
+					  0);
+		varList = list_make1(var);
+
+		/*
+		 * Emit generic record Var so that executor will have the "old" view
+		 * row to pass the RETURNING clause (or upcoming triggers).
+		 */
+		var = makeVar(parsetree->resultRelation,
+					  InvalidAttrNumber,
+					  RECORDOID,
+					  -1,
+					  InvalidOid,
+					  0);
+		varList = lappend(varList, var);
 
-		attrname = "ctid";
+		attNameList = list_make2("rowid", "record");
 	}
 	else
 	{
@@ -1192,16 +1222,21 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 							  parsetree->resultRelation,
 							  0,
 							  false);
-
-		attrname = "wholerow";
+		varList = list_make1(var);
+		attNameList = list_make1("wholerow");
 	}
 
-	tle = makeTargetEntry((Expr *) var,
-						  list_length(parsetree->targetList) + 1,
-						  pstrdup(attrname),
-						  true);
-
-	parsetree->targetList = lappend(parsetree->targetList, tle);
+	/*
+	 * Append them to targetList
+	 */
+	forboth (cell1, varList, cell2, attNameList)
+	{
+		tle = makeTargetEntry((Expr *)lfirst(cell1),
+							  list_length(parsetree->targetList) + 1,
+							  pstrdup((const char *)lfirst(cell2)),
+							  true);
+		parsetree->targetList = lappend(parsetree->targetList, tle);
+	}
 }
 
 
diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
index 721cd25..fa3ffb7 100644
--- a/src/include/foreign/fdwapi.h
+++ b/src/include/foreign/fdwapi.h
@@ -14,6 +14,7 @@
 
 #include "nodes/execnodes.h"
 #include "nodes/relation.h"
+#include "utils/rel.h"
 
 /* To avoid including explain.h here, reference ExplainState thus: */
 struct ExplainState;
@@ -22,6 +23,11 @@ struct ExplainState;
 /*
  * Callback function signatures --- see fdwhandler.sgml for more info.
  */
+typedef AttrNumber (*GetForeignRelWidth_function) (PlannerInfo *root,
+												   RelOptInfo *baserel,
+												   Relation foreignrel,
+												   bool inhparent,
+												   List *targetList);
 
 typedef void (*GetForeignRelSize_function) (PlannerInfo *root,
 														RelOptInfo *baserel,
@@ -58,6 +64,24 @@ typedef int (*AcquireSampleRowsFunc) (Relation relation, int elevel,
 typedef bool (*AnalyzeForeignTable_function) (Relation relation,
 												 AcquireSampleRowsFunc *func,
 													BlockNumber *totalpages);
+typedef List *(*PlanForeignModify_function) (PlannerInfo *root,
+											 ModifyTable *plan,
+											 Index resultRelation,
+											 Plan *subplan);
+
+typedef void (*BeginForeignModify_function) (ModifyTableState *mtstate,
+											 ResultRelInfo *resultRelInfo,
+											 List *fdw_private,
+											 Plan *subplan,
+											 int eflags);
+typedef int	(*ExecForeignInsert_function) (ResultRelInfo *resultRelInfo,
+										   HeapTuple tuple);
+typedef int	(*ExecForeignDelete_function) (ResultRelInfo *resultRelInfo,
+										   const char *rowid);
+typedef int	(*ExecForeignUpdate_function) (ResultRelInfo *resultRelInfo,
+										   const char *rowid,
+										   HeapTuple tuple);
+typedef void (*EndForeignModify_function) (ResultRelInfo *resultRelInfo);
 
 /*
  * FdwRoutine is the struct returned by a foreign-data wrapper's handler
@@ -90,6 +114,13 @@ typedef struct FdwRoutine
 	 * not provided.
 	 */
 	AnalyzeForeignTable_function AnalyzeForeignTable;
+	GetForeignRelWidth_function GetForeignRelWidth;
+	PlanForeignModify_function PlanForeignModify;
+	BeginForeignModify_function	BeginForeignModify;
+	ExecForeignInsert_function ExecForeignInsert;
+	ExecForeignDelete_function ExecForeignDelete;
+	ExecForeignUpdate_function ExecForeignUpdate;
+	EndForeignModify_function EndForeignModify;
 } FdwRoutine;
 
 
diff --git a/src/include/foreign/foreign.h b/src/include/foreign/foreign.h
index f8aa99e..b63000a 100644
--- a/src/include/foreign/foreign.h
+++ b/src/include/foreign/foreign.h
@@ -14,6 +14,8 @@
 #define FOREIGN_H
 
 #include "nodes/parsenodes.h"
+#include "nodes/plannodes.h"
+#include "nodes/relation.h"
 
 
 /* Helper for obtaining username for user mapping */
@@ -81,4 +83,8 @@ extern List *GetForeignColumnOptions(Oid relid, AttrNumber attnum);
 extern Oid	get_foreign_data_wrapper_oid(const char *fdwname, bool missing_ok);
 extern Oid	get_foreign_server_oid(const char *servername, bool missing_ok);
 
+extern AttrNumber get_pseudo_rowid_column(RelOptInfo *baserel,
+										  List *targetList);
+extern ForeignScan *lookup_foreign_scan_plan(Plan *subplan,
+											 Index rtindex);
 #endif   /* FOREIGN_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index d4911bd..18ab231 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -268,9 +268,11 @@ typedef struct ProjectionInfo
  *						attribute numbers of the "original" tuple and the
  *						attribute numbers of the "clean" tuple.
  *	  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
+ *	  jf_junkRowidNo:	not used by junkfilter code.  Can be used by caller
+ *						to remember the attno used to track a particular tuple
+ *						being updated or deleted.
  *						(execMain.c stores the "ctid" attno here).
+ *	  jf_junkRecordNo:	Also, the attno of whole-row reference.
  * ----------------
  */
 typedef struct JunkFilter
@@ -280,7 +282,8 @@ typedef struct JunkFilter
 	TupleDesc	jf_cleanTupType;
 	AttrNumber *jf_cleanMap;
 	TupleTableSlot *jf_resultSlot;
-	AttrNumber	jf_junkAttNo;
+	AttrNumber	jf_junkRowidNo;
+	AttrNumber	jf_junkRecordNo;
 } JunkFilter;
 
 /* ----------------
@@ -303,6 +306,8 @@ typedef struct JunkFilter
  *		ConstraintExprs			array of constraint-checking expr states
  *		junkFilter				for removing junk attributes from tuples
  *		projectReturning		for computing a RETURNING list
+ *		fdwroutine				FDW callbacks if foreign table
+ *		fdw_state				opaque state of FDW module, or NULL
  * ----------------
  */
 typedef struct ResultRelInfo
@@ -320,6 +325,8 @@ typedef struct ResultRelInfo
 	List	  **ri_ConstraintExprs;
 	JunkFilter *ri_junkFilter;
 	ProjectionInfo *ri_projectReturning;
+	struct FdwRoutine  *ri_fdwroutine;
+	void	   *ri_fdw_state;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index fb9a863..93481aa 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -175,6 +175,7 @@ typedef struct ModifyTable
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
 	int			epqParam;		/* ID of Param for EvalPlanQual re-eval */
+	List	   *fdwPrivList;	/* private fields for foreign tables */
 } ModifyTable;
 
 /* ----------------
@@ -478,6 +479,7 @@ typedef struct ForeignScan
 	List	   *fdw_exprs;		/* expressions that FDW may evaluate */
 	List	   *fdw_private;	/* private data for FDW */
 	bool		fsSystemCol;	/* true if any "system column" is needed */
+	bool		fsPseudoCol;	/* true if any "pseudo column" is needed */
 } ForeignScan;
 
 
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index de889fb..adfc93d 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -133,7 +133,7 @@ extern Path *reparameterize_path(PlannerInfo *root, Path *path,
  */
 extern void setup_simple_rel_arrays(PlannerInfo *root);
 extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid,
-				 RelOptKind reloptkind);
+			   List *tlist, RelOptKind reloptkind);
 extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid);
 extern RelOptInfo *find_join_rel(PlannerInfo *root, Relids relids);
 extern RelOptInfo *build_join_rel(PlannerInfo *root,
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index e0d04db..c5fce41 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -26,7 +26,7 @@ extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook;
 
 
 extern void get_relation_info(PlannerInfo *root, Oid relationObjectId,
-				  bool inhparent, RelOptInfo *rel);
+				  bool inhparent, List *tlist, RelOptInfo *rel);
 
 extern void estimate_rel_size(Relation rel, int32 *attr_widths,
 				  BlockNumber *pages, double *tuples, double *allvisfrac);
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 0fe696c..746a603 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);
@@ -90,7 +91,8 @@ extern bool is_projection_capable_plan(Plan *plan);
 extern int	from_collapse_limit;
 extern int	join_collapse_limit;
 
-extern void add_base_rels_to_query(PlannerInfo *root, Node *jtnode);
+extern void add_base_rels_to_query(PlannerInfo *root, List *tlist,
+								   Node *jtnode);
 extern void build_base_rel_tlists(PlannerInfo *root, List *final_tlist);
 extern void add_vars_to_targetlist(PlannerInfo *root, List *vars,
 					   Relids where_needed, bool create_new_ph);
#43Erik Rijkers
er@xs4all.nl
In reply to: Kohei KaiGai (#42)
Re: [v9.3] writable foreign tables

On Wed, December 12, 2012 14:45, Kohei KaiGai wrote:

pgsql-v9.3-writable-fdw-poc.v8.part-2.patch 151 k
pgsql-v9.3-writable-fdw-poc.v8.part-1.patch 70 k

I wanted to have a look at this, and tried to apply part 1, en then part 2 on top of that (that's
the idea, right?)

part 1 applies and then part 2 does not.

Erik Rijkers

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#44Albe Laurenz
laurenz.albe@wien.gv.at
In reply to: Erik Rijkers (#43)
Re: [v9.3] writable foreign tables

Erik Rijkers wrote:

pgsql-v9.3-writable-fdw-poc.v8.part-2.patch 151 k
pgsql-v9.3-writable-fdw-poc.v8.part-1.patch 70 k

I wanted to have a look at this, and tried to apply part 1, en then part 2 on top of that (that's
the idea, right?)

part 1 applies and then part 2 does not.

Part 2 needs this prerequisite:
http://archives.postgresql.org/message-id/CAEZqfEcQtxn1JSjhC5usqhL4_n+Zck3mqo=RzEDFpz+DAWfF_g@mail.gmail.com

Yours,
Laurenz Albe

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#45Albe Laurenz
laurenz.albe@wien.gv.at
In reply to: Kohei KaiGai (#42)
2 attachment(s)
Re: [v9.3] writable foreign tables

Kohei KaiGai wrote:

I came up with one more query that causes a problem:

[...]

This causes a deadlock, but one that is not detected;
the query just keeps hanging.

The UPDATE in the CTE has the rows locked, so the
SELECT ... FOR UPDATE issued via the FDW connection will hang
indefinitely.

I wonder if that's just a pathological corner case that we shouldn't
worry about. Loopback connections for FDWs themselves might not
be so rare, for example as a substitute for autonomous subtransactions.

I guess it is not easily possible to detect such a situation or
to do something reasonable about it.

It is not avoidable problem due to the nature of distributed database system,
not only loopback scenario.

In my personal opinion, I'd like to wait for someone implements distributed
lock/transaction manager on top of the background worker framework being
recently committed, to intermediate lock request.
However, it will take massive amount of efforts to existing lock/transaction
management layer, not only enhancement of FDW APIs. It is obviously out
of scope in this patch.

So, I'd like to suggest authors of FDW that support writable features to put
mention about possible deadlock scenario in their documentation.
At least, above writable CTE example is a situation that two different sessions
concurrently update the "test" relation, thus, one shall be blocked.

Fair enough.

I tried to overhaul the documentation, see the attached patch.

There was one thing that I was not certain of:
You say that for writable foreign tables, BeginForeignModify
and EndForeignModify *must* be implemented.
I thought that these were optional, and if you can do your work

with just, say, ExecForeignDelete, you could do that.

Yes, that's right. What I wrote was incorrect.
If FDW driver does not require any state during modification of
foreign tables, indeed, these are not mandatory handler.

I have updated the documentation, that was all I changed in the
attached patches.

OK. I split the patch into two portion, part-1 is the APIs relevant
patch, part-2 is relevant to postgres_fdw patch.

Great.

I'll mark the patch as "ready for committer".

Yours,
Laurenz Albe

Attachments:

pgsql-v9.3-writable-fdw-poc.v9.part-2.patchapplication/octet-stream; name=pgsql-v9.3-writable-fdw-poc.v9.part-2.patchDownload
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index eab8b87..9ca7fbf 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -43,7 +43,7 @@ typedef struct ConnCacheEntry
 	Oid				serverid;	/* oid of foreign server */
 	Oid				userid;		/* oid of local user */
 
-	bool			use_tx;		/* true when using remote transaction */
+	int				conntx;		/* one of PGSQL_FDW_CONNTX_* */
 	int				refs;		/* reference counter */
 	PGconn		   *conn;		/* foreign server connection */
 } ConnCacheEntry;
@@ -65,6 +65,8 @@ cleanup_connection(ResourceReleasePhase phase,
 static PGconn *connect_pg_server(ForeignServer *server, UserMapping *user);
 static void begin_remote_tx(PGconn *conn);
 static void abort_remote_tx(PGconn *conn);
+static void commit_remote_tx(PGconn *conn);
+static void deallocate_remote_prepare(PGconn *conn);
 
 /*
  * Get a PGconn which can be used to execute foreign query on the remote
@@ -80,7 +82,7 @@ static void abort_remote_tx(PGconn *conn);
  * FDW object to invalidate already established connections.
  */
 PGconn *
-GetConnection(ForeignServer *server, UserMapping *user, bool use_tx)
+GetConnection(ForeignServer *server, UserMapping *user, int conntx)
 {
 	bool			found;
 	ConnCacheEntry *entry;
@@ -126,7 +128,7 @@ GetConnection(ForeignServer *server, UserMapping *user, bool use_tx)
 	entry = hash_search(ConnectionHash, &key, HASH_ENTER, &found);
 	if (!found)
 	{
-		entry->use_tx = false;
+		entry->conntx = PGSQL_FDW_CONNTX_NONE;
 		entry->refs = 0;
 		entry->conn = NULL;
 	}
@@ -162,7 +164,7 @@ GetConnection(ForeignServer *server, UserMapping *user, bool use_tx)
 		{
 			/* Clear connection cache entry on error case. */
 			PQfinish(entry->conn);
-			entry->use_tx = false;
+			entry->conntx = PGSQL_FDW_CONNTX_NONE;
 			entry->refs = 0;
 			entry->conn = NULL;
 			PG_RE_THROW();
@@ -182,10 +184,11 @@ GetConnection(ForeignServer *server, UserMapping *user, bool use_tx)
 	 * are in.  We need to remember whether this connection uses remote
 	 * transaction to abort it when this connection is released completely.
 	 */
-	if (use_tx && !entry->use_tx)
+	if (conntx > entry->conntx)
 	{
-		begin_remote_tx(entry->conn);
-		entry->use_tx = use_tx;
+		if (entry->conntx == PGSQL_FDW_CONNTX_NONE)
+			begin_remote_tx(entry->conn);
+		entry->conntx = conntx;
 	}
 
 	return entry->conn;
@@ -355,12 +358,45 @@ abort_remote_tx(PGconn *conn)
 	PQclear(res);
 }
 
+static void
+commit_remote_tx(PGconn *conn)
+{
+	PGresult	   *res;
+
+	elog(DEBUG3, "committing remote transaction");
+
+	res = PQexec(conn, "COMMIT TRANSACTION");
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		PQclear(res);
+		elog(ERROR, "could not commit transaction: %s", PQerrorMessage(conn));
+	}
+	PQclear(res);
+}
+
+static void
+deallocate_remote_prepare(PGconn *conn)
+{
+	PGresult	   *res;
+
+	elog(DEBUG3, "deallocating remote prepares");
+
+	res = PQexec(conn, "DEALLOCATE PREPARE ALL");
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		PQclear(res);
+		elog(ERROR, "could not deallocate prepared statement: %s",
+			 PQerrorMessage(conn));
+	}
+	PQclear(res);
+}
+
 /*
  * Mark the connection as "unused", and close it if the caller was the last
  * user of the connection.
  */
 void
-ReleaseConnection(PGconn *conn)
+ReleaseConnection(PGconn *conn, bool is_abort)
 {
 	HASH_SEQ_STATUS		scan;
 	ConnCacheEntry	   *entry;
@@ -412,7 +448,7 @@ ReleaseConnection(PGconn *conn)
 			 PQtransactionStatus(conn) == PQTRANS_INERROR ? "INERROR" :
 			 "UNKNOWN");
 		PQfinish(conn);
-		entry->use_tx = false;
+		entry->conntx = PGSQL_FDW_CONNTX_NONE;
 		entry->refs = 0;
 		entry->conn = NULL;
 		return;
@@ -430,10 +466,16 @@ ReleaseConnection(PGconn *conn)
 	 * If this connection uses remote transaction and there is no user other
 	 * than the caller, abort the remote transaction and forget about it.
 	 */
-	if (entry->use_tx && entry->refs == 0)
+	if (entry->conntx > PGSQL_FDW_CONNTX_NONE && entry->refs == 0)
 	{
-		abort_remote_tx(conn);
-		entry->use_tx = false;
+		if (entry->conntx > PGSQL_FDW_CONNTX_READ_ONLY)
+			deallocate_remote_prepare(conn);
+		if (is_abort || entry->conntx == PGSQL_FDW_CONNTX_READ_ONLY)
+			abort_remote_tx(conn);
+		else
+			commit_remote_tx(conn);
+
+		entry->conntx = PGSQL_FDW_CONNTX_NONE;
 	}
 }
 
@@ -485,7 +527,7 @@ cleanup_connection(ResourceReleasePhase phase,
 		elog(DEBUG3, "discard postgres_fdw connection %p due to resowner cleanup",
 			 entry->conn);
 		PQfinish(entry->conn);
-		entry->use_tx = false;
+		entry->conntx = PGSQL_FDW_CONNTX_NONE;
 		entry->refs = 0;
 		entry->conn = NULL;
 	}
@@ -597,7 +639,7 @@ postgres_fdw_disconnect(PG_FUNCTION_ARGS)
 
 	/* Discard cached connection, and clear reference counter. */
 	PQfinish(entry->conn);
-	entry->use_tx = false;
+	entry->conntx = PGSQL_FDW_CONNTX_NONE;
 	entry->refs = 0;
 	entry->conn = NULL;
 
diff --git a/contrib/postgres_fdw/connection.h b/contrib/postgres_fdw/connection.h
index 4c9d850..f97cc8a 100644
--- a/contrib/postgres_fdw/connection.h
+++ b/contrib/postgres_fdw/connection.h
@@ -16,10 +16,14 @@
 #include "foreign/foreign.h"
 #include "libpq-fe.h"
 
+#define PGSQL_FDW_CONNTX_NONE			0
+#define PGSQL_FDW_CONNTX_READ_ONLY		1
+#define PGSQL_FDW_CONNTX_READ_WRITE		2
+
 /*
  * Connection management
  */
-PGconn *GetConnection(ForeignServer *server, UserMapping *user, bool use_tx);
-void ReleaseConnection(PGconn *conn);
+PGconn *GetConnection(ForeignServer *server, UserMapping *user, int conntx);
+void ReleaseConnection(PGconn *conn, bool is_abort);
 
 #endif /* CONNECTION_H */
diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
index 69e6a3e..9e09429 100644
--- a/contrib/postgres_fdw/deparse.c
+++ b/contrib/postgres_fdw/deparse.c
@@ -12,6 +12,7 @@
  */
 #include "postgres.h"
 
+#include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/transam.h"
 #include "catalog/pg_class.h"
@@ -86,9 +87,11 @@ void
 deparseSimpleSql(StringInfo buf,
 				 PlannerInfo *root,
 				 RelOptInfo *baserel,
-				 List *local_conds)
+				 List *local_conds,
+				 AttrNumber anum_rowid)
 {
 	RangeTblEntry *rte;
+	Relation	rel;
 	ListCell   *lc;
 	StringInfoData	foreign_relname;
 	bool		first;
@@ -125,6 +128,24 @@ deparseSimpleSql(StringInfo buf,
 	}
 
 	/*
+	 * XXX - When this foreign table is target relation and RETURNING
+	 * clause reference some column, we have to mark these columns as
+	 * in-use. It is needed to support DELETE command, because INSERT
+	 * and UPDATE implicitly add references to all the regular columns
+	 * on baserel->reltargetlist.
+	 */
+	if (root->parse->resultRelation == baserel->relid &&
+		root->parse->returningList)
+	{
+		List   *attrs;
+
+		attrs = pull_var_clause((Node *) root->parse->returningList,
+								PVC_RECURSE_AGGREGATES,
+                                PVC_RECURSE_PLACEHOLDERS);
+		attr_used = list_union(attr_used, attrs);
+	}
+
+	/*
 	 * deparse SELECT clause
 	 *
 	 * List attributes which are in either target list or local restriction.
@@ -136,9 +157,10 @@ deparseSimpleSql(StringInfo buf,
 	 */
 	appendStringInfo(buf, "SELECT ");
 	rte = root->simple_rte_array[baserel->relid];
+	rel = heap_open(rte->relid, NoLock);
 	attr_used = list_union(attr_used, baserel->reltargetlist);
 	first = true;
-	for (attr = 1; attr <= baserel->max_attr; attr++)
+	for (attr = 1; attr <= RelationGetNumberOfAttributes(rel); attr++)
 	{
 		Var		   *var = NULL;
 		ListCell   *lc;
@@ -167,6 +189,10 @@ deparseSimpleSql(StringInfo buf,
 		else
 			appendStringInfo(buf, "NULL");
 	}
+	if (anum_rowid != InvalidAttrNumber)
+		appendStringInfo(buf, "%sctid", (first ? "" : ","));
+
+	heap_close(rel, NoLock);
 	appendStringInfoChar(buf, ' ');
 
 	/*
@@ -283,6 +309,102 @@ deparseAnalyzeSql(StringInfo buf, Relation rel)
 }
 
 /*
+ * deparse remote INSERT statement
+ */
+void
+deparseInsertSql(StringInfo buf, PlannerInfo *root, Index rtindex)
+{
+	RangeTblEntry  *rte = root->simple_rte_array[rtindex];
+	Relation		frel = heap_open(rte->relid, NoLock);
+	int				i, j, nattrs = RelationGetNumberOfAttributes(frel);
+	bool			is_first = true;
+
+	appendStringInfo(buf, "INSERT INTO ");
+	deparseRelation(buf, rte);
+	appendStringInfo(buf, "(");
+
+	for (i=0; i < nattrs; i++)
+	{
+		Form_pg_attribute attr = RelationGetDescr(frel)->attrs[i];
+		Var		var;
+
+		if (attr->attisdropped)
+			continue;
+
+		if (!is_first)
+			appendStringInfo(buf, ",");
+
+		var.varno = rtindex;
+		var.varattno = attr->attnum;
+		deparseVar(buf, &var, root);
+		is_first = false;
+	}
+	appendStringInfo(buf, ") VALUES (");
+
+	for (i=0, j=1; i < nattrs; i++)
+	{
+		Form_pg_attribute attr = RelationGetDescr(frel)->attrs[i];
+
+		if (attr->attisdropped)
+			continue;
+
+		appendStringInfo(buf, "%s$%d", (j == 1 ? "" : ","), j);
+		j++;
+	}
+	appendStringInfo(buf, ")");
+	heap_close(frel, NoLock);
+}
+
+/*
+ * deparse remote UPDATE statement
+ */
+void
+deparseUpdateSql(StringInfo buf, PlannerInfo *root, Index rtindex)
+{
+	RangeTblEntry  *rte = root->simple_rte_array[rtindex];
+	Relation		frel = heap_open(rte->relid, NoLock);
+	int				i, j, nattrs = RelationGetNumberOfAttributes(frel);
+	bool			is_first = true;
+
+	appendStringInfo(buf, "UPDATE ");
+	deparseRelation(buf, rte);
+	appendStringInfo(buf, " SET ");
+
+	for (i=0, j=2; i < nattrs; i++)
+	{
+		Form_pg_attribute attr = RelationGetDescr(frel)->attrs[i];
+		Var		var;
+
+		if (attr->attisdropped)
+			continue;
+
+		if (!is_first)
+			appendStringInfo(buf, ",");
+
+		var.varno = rtindex;
+		var.varattno = attr->attnum;
+		deparseVar(buf, &var, root);
+		appendStringInfo(buf, "=$%d", j++);
+		is_first = false;
+	}
+	appendStringInfo(buf, " WHERE ctid=$1");
+	heap_close(frel, NoLock);
+}
+
+/*
+ * deparse remote DELETE statement
+ */
+void
+deparseDeleteSql(StringInfo buf, PlannerInfo *root, Index rtindex)
+{
+	RangeTblEntry  *rte = root->simple_rte_array[rtindex];
+
+	appendStringInfo(buf, "DELETE FROM ");
+	deparseRelation(buf, rte);
+	appendStringInfo(buf, " WHERE ctid = $1");
+}
+
+/*
  * Deparse given expression into buf.  Actual string operation is delegated to
  * node-type-specific functions.
  *
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index f81c727..25269a8 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -736,6 +736,1104 @@ SELECT srvname FROM postgres_fdw_connections;
 (0 rows)
 
 -- ===================================================================
+-- test for writable foreign table stuff (PoC stage now)
+-- ===================================================================
+EXPLAIN(verbose) INSERT INTO ft2 (c1,c2,c3) (SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20);
+                                                                                                          QUERY PLAN                                                                                                           
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Insert on public.ft2  (cost=100.00..100.03 rows=1 width=40)
+   ->  Subquery Scan on "*SELECT*"  (cost=100.00..100.03 rows=1 width=40)
+         Output: NULL::integer, "*SELECT*"."?column?", "*SELECT*"."?column?", "*SELECT*"."?column?", NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying, NULL::bpchar, NULL::user_enum
+         ->  Limit  (cost=100.00..100.02 rows=1 width=40)
+               Output: ((ft2_1.c1 + 1000)), ((ft2_1.c2 + 100)), ((ft2_1.c3 || ft2_1.c3))
+               ->  Foreign Scan on public.ft2 ft2_1  (cost=100.00..100.02 rows=1 width=40)
+                     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"
+(8 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) 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  (cost=200.00..200.04 rows=1 width=344)
+   ->  Nested Loop  (cost=200.00..200.04 rows=1 width=344)
+         Output: NULL::integer, ft2.c1, (ft2.c2 + 500), (ft2.c3 || '_update9'::text), ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid, ft2.*, ft1.*
+         Join Filter: (ft2.c2 = ft1.c1)
+         ->  Foreign Scan on public.ft2  (cost=100.00..100.01 rows=1 width=232)
+               Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid, ft2.*
+               Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8,ctid FROM "S 1"."T 1" FOR UPDATE
+         ->  Foreign Scan on public.ft1  (cost=100.00..100.01 rows=1 width=116)
+               Output: ft1.*, ft1.c1
+               Remote SQL: SELECT "C 1", NULL, NULL, NULL, NULL, NULL, NULL, NULL FROM "S 1"."T 1" WHERE ((("C 1" OPERATOR(pg_catalog.%) 10) OPERATOR(pg_catalog.=) 9))
+(10 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) DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2;
+                                                                               QUERY PLAN                                                                               
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Delete on public.ft2  (cost=200.00..200.03 rows=1 width=256)
+   ->  Nested Loop  (cost=200.00..200.03 rows=1 width=256)
+         Output: ft2.ctid, ft2.*, ft1.*
+         Join Filter: (ft2.c2 = ft1.c1)
+         ->  Foreign Scan on public.ft2  (cost=100.00..100.01 rows=1 width=148)
+               Output: ft2.ctid, ft2.*, ft2.c2
+               Remote SQL: SELECT NULL, c2, NULL, NULL, NULL, NULL, NULL, NULL,ctid FROM "S 1"."T 1" FOR UPDATE
+         ->  Foreign Scan on public.ft1  (cost=100.00..100.01 rows=1 width=116)
+               Output: ft1.*, ft1.c1
+               Remote SQL: SELECT "C 1", NULL, NULL, NULL, NULL, NULL, NULL, NULL FROM "S 1"."T 1" WHERE ((("C 1" OPERATOR(pg_catalog.%) 10) OPERATOR(pg_catalog.=) 2))
+(10 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)
+
+-- ===================================================================
 -- cleanup
 -- ===================================================================
 DROP OPERATOR === (int, int) CASCADE;
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 6b870ab..75439b1 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -27,6 +27,7 @@
 #include "optimizer/pathnode.h"
 #include "optimizer/planmain.h"
 #include "optimizer/restrictinfo.h"
+#include "parser/parsetree.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -59,6 +60,7 @@ typedef struct PostgresFdwPlanState {
 	List		   *param_conds;
 	List		   *local_conds;
 	int				width;			/* obtained by remote EXPLAIN */
+	AttrNumber		anum_rowid;
 
 	/* Cached catalog information. */
 	ForeignTable   *table;
@@ -150,6 +152,20 @@ typedef struct PostgresAnalyzeState
 } PostgresAnalyzeState;
 
 /*
+ * Describes a state of modify request for a foreign table
+ */
+typedef struct PostgresFdwModifyState
+{
+	PGconn	   *conn;
+	char	   *query;
+	char	   *p_name;
+	int			p_nums;
+	Oid		   *p_types;
+	FmgrInfo   *p_flinfo;
+	MemoryContext	es_query_cxt;
+} PostgresFdwModifyState;
+
+/*
  * SQL functions
  */
 extern Datum postgres_fdw_handler(PG_FUNCTION_ARGS);
@@ -158,6 +174,11 @@ PG_FUNCTION_INFO_V1(postgres_fdw_handler);
 /*
  * FDW callback routines
  */
+static AttrNumber postgresGetForeignRelWidth(PlannerInfo *root,
+											 RelOptInfo *baserel,
+											 Relation foreignrel,
+											 bool inhparent,
+											 List *targetList);
 static void postgresGetForeignRelSize(PlannerInfo *root,
 									  RelOptInfo *baserel,
 									  Oid foreigntableid);
@@ -179,6 +200,23 @@ static void postgresEndForeignScan(ForeignScanState *node);
 static bool postgresAnalyzeForeignTable(Relation relation,
 										AcquireSampleRowsFunc *func,
 										BlockNumber *totalpages);
+static List *postgresPlanForeignModify(PlannerInfo *root,
+									   ModifyTable *plan,
+									   Index resultRelation,
+									   Plan *subplan);
+static void postgresBeginForeignModify(ModifyTableState *mtstate,
+									   ResultRelInfo *resultRelInfo,
+									   List *fdw_private,
+									   Plan *subplan,
+									   int eflags);
+static int postgresExecForeignInsert(ResultRelInfo *resultRelInfo,
+									 HeapTuple tuple);
+static int postgresExecForeignDelete(ResultRelInfo *resultRelInfo,
+									 const char *rowid);
+static int postgresExecForeignUpdate(ResultRelInfo *resultRelInfo,
+									 const char *rowid,
+									 HeapTuple tuple);
+static void postgresEndForeignModify(ResultRelInfo *resultRelInfo);
 
 /*
  * Helper functions
@@ -231,6 +269,7 @@ postgres_fdw_handler(PG_FUNCTION_ARGS)
 	FdwRoutine	*routine = makeNode(FdwRoutine);
 
 	/* Required handler functions. */
+	routine->GetForeignRelWidth = postgresGetForeignRelWidth;
 	routine->GetForeignRelSize = postgresGetForeignRelSize;
 	routine->GetForeignPaths = postgresGetForeignPaths;
 	routine->GetForeignPlan = postgresGetForeignPlan;
@@ -239,6 +278,12 @@ postgres_fdw_handler(PG_FUNCTION_ARGS)
 	routine->IterateForeignScan = postgresIterateForeignScan;
 	routine->ReScanForeignScan = postgresReScanForeignScan;
 	routine->EndForeignScan = postgresEndForeignScan;
+	routine->PlanForeignModify = postgresPlanForeignModify;
+	routine->BeginForeignModify = postgresBeginForeignModify;
+	routine->ExecForeignInsert = postgresExecForeignInsert;
+	routine->ExecForeignDelete = postgresExecForeignDelete;
+	routine->ExecForeignUpdate = postgresExecForeignUpdate;
+	routine->EndForeignModify = postgresEndForeignModify;
 
 	/* Optional handler functions. */
 	routine->AnalyzeForeignTable = postgresAnalyzeForeignTable;
@@ -247,6 +292,34 @@ postgres_fdw_handler(PG_FUNCTION_ARGS)
 }
 
 /*
+ * postgresGetForeignRelWidth
+ *		Informs how many columns (including pseudo ones) are needed.
+ */
+static AttrNumber
+postgresGetForeignRelWidth(PlannerInfo *root,
+						   RelOptInfo *baserel,
+						   Relation foreignrel,
+						   bool inhparent,
+						   List *targetList)
+{
+	PostgresFdwPlanState *fpstate = palloc0(sizeof(PostgresFdwPlanState));
+
+	baserel->fdw_private = fpstate;
+
+	/* does rowid pseudo-column is required? */
+	fpstate->anum_rowid = get_pseudo_rowid_column(baserel, targetList);
+	if (fpstate->anum_rowid != InvalidAttrNumber)
+	{
+		RangeTblEntry *rte = rt_fetch(baserel->relid,
+									  root->parse->rtable);
+		rte->eref->colnames = lappend(rte->eref->colnames,
+									  makeString("ctid"));
+		return fpstate->anum_rowid;
+	}
+	return RelationGetNumberOfAttributes(foreignrel);
+}
+
+/*
  * postgresGetForeignRelSize
  *		Estimate # of rows and width of the result of the scan
  *
@@ -283,7 +356,7 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	 * We use PostgresFdwPlanState to pass various information to subsequent
 	 * functions.
 	 */
-	fpstate = palloc0(sizeof(PostgresFdwPlanState));
+	fpstate = baserel->fdw_private;
 	initStringInfo(&fpstate->sql);
 	sql = &fpstate->sql;
 
@@ -320,10 +393,9 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	 */
 	classifyConditions(root, baserel, &remote_conds, &param_conds,
 					   &local_conds);
-	deparseSimpleSql(sql, root, baserel, local_conds);
+	deparseSimpleSql(sql, root, baserel, local_conds, fpstate->anum_rowid);
 	if (list_length(remote_conds) > 0)
 		appendWhereClause(sql, true, remote_conds, root);
-	elog(DEBUG3, "Query SQL: %s", sql->data);
 
 	/*
 	 * If the table or the server is configured to use remote EXPLAIN, connect
@@ -337,10 +409,10 @@ postgresGetForeignRelSize(PlannerInfo *root,
 		PGconn		   *conn;
 
 		user = GetUserMapping(GetOuterUserId(), server->serverid);
-		conn = GetConnection(server, user, false);
+		conn = GetConnection(server, user, PGSQL_FDW_CONNTX_NONE);
 		get_remote_estimate(sql->data, conn, &rows, &width,
 							&startup_cost, &total_cost);
-		ReleaseConnection(conn);
+		ReleaseConnection(conn, false);
 
 		/*
 		 * Estimate selectivity of conditions which are not used in remote
@@ -391,7 +463,6 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	fpstate->width = width;
 	fpstate->table = table;
 	fpstate->server = server;
-	baserel->fdw_private = (void *) fpstate;
 }
 
 /*
@@ -592,7 +663,7 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 	table = GetForeignTable(relid);
 	server = GetForeignServer(table->serverid);
 	user = GetUserMapping(GetOuterUserId(), server->serverid);
-	conn = GetConnection(server, user, true);
+	conn = GetConnection(server, user, PGSQL_FDW_CONNTX_READ_ONLY);
 	festate->conn = conn;
 
 	/* Result will be filled in first Iterate call. */
@@ -724,7 +795,7 @@ postgresEndForeignScan(ForeignScanState *node)
 	 * end of the scan to make the lifespan of remote transaction same as the
 	 * local query.
 	 */
-	ReleaseConnection(festate->conn);
+	ReleaseConnection(festate->conn, false);
 	festate->conn = NULL;
 
 	/* Discard fetch results */
@@ -790,7 +861,7 @@ get_remote_estimate(const char *sql, PGconn *conn,
 		PQclear(res);
 
 		/* Release connection and let connection manager cleanup. */
-		ReleaseConnection(conn);
+		ReleaseConnection(conn, true);
 
 		PG_RE_THROW();
 	}
@@ -947,7 +1018,7 @@ execute_query(ForeignScanState *node)
 		PQclear(res);
 
 		/* Release connection and let connection manager cleanup. */
-		ReleaseConnection(conn);
+		ReleaseConnection(conn, true);
 
 		/* propagate error */
 		PG_RE_THROW();
@@ -1105,6 +1176,8 @@ postgres_fdw_error_callback(void *arg)
 
 	relname = get_rel_name(errpos->relid);
 	colname = get_attname(errpos->relid, errpos->cur_attno);
+	if (!colname)
+		colname = "pseudo-column";
 	errcontext("column %s of foreign table %s",
 			   quote_identifier(colname), quote_identifier(relname));
 }
@@ -1172,7 +1245,7 @@ postgresAcquireSampleRowsFunc(Relation relation, int elevel,
 	table = GetForeignTable(relation->rd_id);
 	server = GetForeignServer(table->serverid);
 	user = GetUserMapping(GetOuterUserId(), server->serverid);
-	conn = GetConnection(server, user, true);
+	conn = GetConnection(server, user, PGSQL_FDW_CONNTX_READ_ONLY);
 
 	/*
 	 * Acquire sample rows from the result set.
@@ -1239,13 +1312,13 @@ postgresAcquireSampleRowsFunc(Relation relation, int elevel,
 		PQclear(res);
 
 		/* Release connection and let connection manager cleanup. */
-		ReleaseConnection(conn);
+		ReleaseConnection(conn, true);
 
 		PG_RE_THROW();
 	}
 	PG_END_TRY();
 
-	ReleaseConnection(conn);
+	ReleaseConnection(conn, false);
 
 	/* We assume that we have no dead tuple. */
 	*totaldeadrows = 0.0;
@@ -1429,3 +1502,318 @@ analyze_row_processor(PGresult *res, PostgresAnalyzeState *astate, bool first)
 
 	return;
 }
+
+static List *
+postgresPlanForeignModify(PlannerInfo *root,
+						  ModifyTable *plan,
+						  Index resultRelation,
+						  Plan *subplan)
+{
+	CmdType			operation = plan->operation;
+	StringInfoData	sql;
+
+	initStringInfo(&sql);
+
+	/*
+	 * XXX - In case of UPDATE or DELETE commands are quite "simple",
+	 * we will be able to execute raw UPDATE or DELETE statement at
+	 * the stage of scan, instead of combination SELECT ... FOR UPDATE
+	 * and either of UPDATE or DELETE commands.
+	 * It should be an idea of optimization in the future version.
+	 *
+	 * XXX - FOR UPDATE should be appended on the remote query of scan
+	 * stage to avoid unexpected concurrent update on the target rows.
+	 */
+	if (operation == CMD_UPDATE || operation == CMD_DELETE)
+	{
+		ForeignScan	   *fscan;
+		Value		   *select_sql;
+
+		fscan = lookup_foreign_scan_plan(subplan, resultRelation);
+		if (!fscan)
+			elog(ERROR, "no underlying scan plan found in subplan tree");
+
+		select_sql = list_nth(fscan->fdw_private,
+							  FdwPrivateSelectSql);
+		appendStringInfo(&sql, "%s FOR UPDATE", strVal(select_sql));
+		strVal(select_sql) = pstrdup(sql.data);
+
+		resetStringInfo(&sql);
+	}
+
+	switch (operation)
+	{
+		case CMD_INSERT:
+			deparseInsertSql(&sql, root, resultRelation);
+			elog(DEBUG3, "Remote INSERT query: %s", sql.data);
+			break;
+		case CMD_UPDATE:
+			deparseUpdateSql(&sql, root, resultRelation);
+			elog(DEBUG3, "Remote UPDATE query: %s", sql.data);
+			break;
+		case CMD_DELETE:
+			deparseDeleteSql(&sql, root, resultRelation);
+			elog(DEBUG3, "Remote DELETE query: %s", sql.data);
+			break;
+		default:
+			elog(ERROR, "unexpected operation: %d", (int) operation);
+	}
+	return list_make1(makeString(sql.data));
+}
+
+static void
+postgresBeginForeignModify(ModifyTableState *mtstate,
+						   ResultRelInfo *resultRelInfo,
+						   List *fdw_private,
+						   Plan *subplan,
+						   int eflags)
+{
+	PostgresFdwModifyState *fmstate;
+	CmdType			operation = mtstate->operation;
+	Relation		frel = resultRelInfo->ri_RelationDesc;
+	AttrNumber		n_params = RelationGetNumberOfAttributes(frel) + 1;
+	ForeignTable   *ftable;
+	ForeignServer  *fserver;
+	UserMapping	   *fuser;
+	Oid				out_func_oid;
+	bool			isvarlena;
+
+	/*
+	 * Construct PostgresFdwExecutionState
+	 */
+	fmstate = palloc0(sizeof(PostgresFdwExecutionState));
+
+	ftable = GetForeignTable(RelationGetRelid(frel));
+	fserver = GetForeignServer(ftable->serverid);
+	fuser = GetUserMapping(GetOuterUserId(), fserver->serverid);
+
+	fmstate->query = strVal(linitial(fdw_private));
+	fmstate->conn = GetConnection(fserver, fuser,
+								  PGSQL_FDW_CONNTX_READ_WRITE);
+	fmstate->p_name = NULL;
+	fmstate->p_types = palloc0(sizeof(Oid) * n_params);
+	fmstate->p_flinfo = palloc0(sizeof(FmgrInfo) * n_params);
+
+	/* 1st parameter should be ctid on UPDATE or DELETE */
+	if (operation == CMD_UPDATE || operation == CMD_DELETE)
+	{
+		fmstate->p_types[fmstate->p_nums] = TIDOID;
+		getTypeOutputInfo(TIDOID, &out_func_oid, &isvarlena);
+		fmgr_info(out_func_oid, &fmstate->p_flinfo[fmstate->p_nums]);
+		fmstate->p_nums++;
+	}
+	/* following parameters should be regular columns */
+	if (operation == CMD_UPDATE || operation == CMD_INSERT)
+	{
+		AttrNumber	index;
+
+		for (index=0; index < RelationGetNumberOfAttributes(frel); index++)
+		{
+			Form_pg_attribute	attr = RelationGetDescr(frel)->attrs[index];
+
+			if (attr->attisdropped)
+				continue;
+
+			fmstate->p_types[fmstate->p_nums] = attr->atttypid;
+			getTypeOutputInfo(attr->atttypid, &out_func_oid, &isvarlena);
+			fmgr_info(out_func_oid, &fmstate->p_flinfo[fmstate->p_nums]);
+			fmstate->p_nums++;
+		}
+	}
+	Assert(fmstate->p_nums <= n_params);
+	fmstate->es_query_cxt = mtstate->ps.state->es_query_cxt;
+	resultRelInfo->ri_fdw_state = fmstate;
+}
+
+static void
+prepare_foreign_modify(PostgresFdwModifyState *fmstate)
+{
+	static int	prep_id = 1;
+	char		prep_name[NAMEDATALEN];
+	PGresult   *res;
+
+	snprintf(prep_name, sizeof(prep_name),
+			 "pgsql_fdw_prep_%08x", prep_id++);
+
+	res = PQprepare(fmstate->conn,
+					prep_name,
+					fmstate->query,
+					fmstate->p_nums,
+					fmstate->p_types);
+	if (!res || PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		PQclear(res);
+		elog(ERROR, "could not prepare statement (%s): %s",
+			 fmstate->query, PQerrorMessage(fmstate->conn));
+	}
+	PQclear(res);
+
+	fmstate->p_name = MemoryContextStrdup(fmstate->es_query_cxt, prep_name);
+}
+
+static int
+setup_exec_prepared(ResultRelInfo *resultRelInfo,
+					const char *rowid, HeapTuple tuple,
+					const char *p_values[], int p_lengths[])
+{
+	PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state;
+	Relation	frel = resultRelInfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(frel);
+	int			i, p_idx = 0;
+
+	/* 1st parameter should be ctid */
+	if (rowid)
+	{
+		p_values[p_idx] = rowid;
+		p_lengths[p_idx] = strlen(rowid) + 1;
+		p_idx++;
+	}
+
+	/* following parameters are as TupleDesc */
+	if (HeapTupleIsValid(tuple))
+	{
+		for (i = 0; i < tupdesc->natts; i++)
+		{
+			Form_pg_attribute	attr = tupdesc->attrs[i];
+			Datum		value;
+			bool		isnull;
+
+			if (attr->attisdropped)
+				continue;
+
+			value = heap_getattr(tuple, attr->attnum, tupdesc, &isnull);
+			if (isnull)
+			{
+				p_values[p_idx] = NULL;
+				p_lengths[p_idx] = 0;
+			}
+			else
+			{
+				p_values[p_idx] =
+					OutputFunctionCall(&fmstate->p_flinfo[p_idx], value);
+				p_lengths[p_idx] = strlen(p_values[p_idx]) + 1;
+			}
+			p_idx++;
+		}
+	}
+	return p_idx;
+}
+
+static int
+postgresExecForeignInsert(ResultRelInfo *resultRelInfo,
+						  HeapTuple tuple)
+{
+	PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state;
+	const char	  **p_values  = alloca(sizeof(char *) * fmstate->p_nums);
+	int			   *p_lengths = alloca(sizeof(int) * fmstate->p_nums);
+	AttrNumber		nattrs;
+	PGresult	   *res;
+	int				n_rows;
+
+	if (!fmstate->p_name)
+		prepare_foreign_modify(fmstate);
+
+	nattrs = setup_exec_prepared(resultRelInfo,
+								 NULL, tuple,
+								 p_values, p_lengths);
+	Assert(fmstate->p_nums == nattrs);
+
+	res = PQexecPrepared(fmstate->conn,
+						 fmstate->p_name,
+						 nattrs,
+						 p_values,
+						 p_lengths,
+						 NULL, 0);
+	if (!res ||  PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		PQclear(res);
+		elog(ERROR, "could not execute prepared statement (%s): %s",
+			 fmstate->query, PQerrorMessage(fmstate->conn));
+    }
+	n_rows = atoi(PQcmdTuples(res));
+    PQclear(res);
+
+	return n_rows;
+}
+
+static int
+postgresExecForeignDelete(ResultRelInfo *resultRelInfo, const char *rowid)
+{
+	PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state;
+	const char	   *p_values[1];
+	int				p_lengths[1];
+	AttrNumber		nattrs;
+	PGresult	   *res;
+	int				n_rows;
+
+	if (!fmstate->p_name)
+		prepare_foreign_modify(fmstate);
+
+	nattrs = setup_exec_prepared(resultRelInfo,
+								 rowid, NULL,
+								 p_values, p_lengths);
+	Assert(fmstate->p_nums == nattrs);
+
+	res = PQexecPrepared(fmstate->conn,
+						 fmstate->p_name,
+						 nattrs,
+						 p_values,
+						 p_lengths,
+						 NULL, 0);
+	if (!res ||  PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		PQclear(res);
+		elog(ERROR, "could not execute prepared statement (%s): %s",
+			 fmstate->query, PQerrorMessage(fmstate->conn));
+    }
+	n_rows = atoi(PQcmdTuples(res));
+    PQclear(res);
+
+	return n_rows;
+}
+
+static int
+postgresExecForeignUpdate(ResultRelInfo *resultRelInfo,
+						  const char *rowid, HeapTuple tuple)
+{
+	PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state;
+	const char	  **p_values  = alloca(sizeof(char *) * (fmstate->p_nums + 1));
+	int			   *p_lengths = alloca(sizeof(int) * (fmstate->p_nums + 1));
+	AttrNumber		nattrs;
+	PGresult	   *res;
+	int				n_rows;
+
+	if (!fmstate->p_name)
+		prepare_foreign_modify(fmstate);
+
+	nattrs = setup_exec_prepared(resultRelInfo,
+								 rowid, tuple,
+								 p_values, p_lengths);
+	Assert(fmstate->p_nums == nattrs);
+
+	res = PQexecPrepared(fmstate->conn,
+						 fmstate->p_name,
+						 nattrs,
+						 p_values,
+						 p_lengths,
+						 NULL, 0);
+	if (!res ||  PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		PQclear(res);
+		elog(ERROR, "could not execute prepared statement (%s): %s",
+			 fmstate->query, PQerrorMessage(fmstate->conn));
+    }
+	n_rows = atoi(PQcmdTuples(res));
+    PQclear(res);
+
+	return n_rows;
+}
+
+static void
+postgresEndForeignModify(ResultRelInfo *resultRelInfo)
+{
+	PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state;
+
+	ReleaseConnection(fmstate->conn, false);
+	fmstate->conn = NULL;
+}
diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h
index b5cefb8..3756a0d 100644
--- a/contrib/postgres_fdw/postgres_fdw.h
+++ b/contrib/postgres_fdw/postgres_fdw.h
@@ -30,7 +30,8 @@ int GetFetchCountOption(ForeignTable *table, ForeignServer *server);
 void deparseSimpleSql(StringInfo buf,
 					  PlannerInfo *root,
 					  RelOptInfo *baserel,
-					  List *local_conds);
+					  List *local_conds,
+					  AttrNumber anum_rowid);
 void appendWhereClause(StringInfo buf,
 					   bool has_where,
 					   List *exprs,
@@ -41,5 +42,8 @@ void classifyConditions(PlannerInfo *root,
 						List **param_conds,
 						List **local_conds);
 void deparseAnalyzeSql(StringInfo buf, Relation rel);
+void deparseInsertSql(StringInfo buf, PlannerInfo *root, Index rtindex);
+void deparseUpdateSql(StringInfo buf, PlannerInfo *root, Index rtindex);
+void deparseDeleteSql(StringInfo buf, PlannerInfo *root, Index rtindex);
 
 #endif /* POSTGRESQL_FDW_H */
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 7845e70..30fcef6 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -300,6 +300,22 @@ ERROR OUT;          -- ERROR
 SELECT srvname FROM postgres_fdw_connections;
 
 -- ===================================================================
+-- test for writable foreign table stuff (PoC stage now)
+-- ===================================================================
+EXPLAIN(verbose) 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) 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) 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;
+
+-- ===================================================================
 -- cleanup
 -- ===================================================================
 DROP OPERATOR === (int, int) CASCADE;
pgsql-v9.3-writable-fdw-poc.v9.part-1.patchapplication/octet-stream; name=pgsql-v9.3-writable-fdw-poc.v9.part-1.patchDownload
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index d6e5d64..30f9c6b 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -3066,11 +3066,6 @@ ANALYZE measurement;
     <firstterm>user mapping</>, which can provide additional options 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.
-   </para>
  </sect1>
 
  <sect1 id="ddl-others">
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index 2d604ed..c4c05e6 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -89,6 +89,54 @@
 
     <para>
 <programlisting>
+AttrNumber
+GetForeignRelWidth(PlannerInfo *root,
+                   RelOptInfo *baserel,
+                   Relation foreignrel,
+                   bool inhparent,
+                   List *targetList);
+</programlisting>
+     Obtain the width of the result set to be fetched during a foreign table scan.
+     This is an optional handler, and called before <literal>GetForeignRelSize</>
+     for a query involving a foreign table
+     (during the construction of <literal>RelOptInfo</>).
+     <literal>root</> is the planner's global information about the query,
+     <literal>baserel</> is the planner's information being constructed for
+     this query, and <literal>foreignrel</> is a <literal>Relation</>
+     descriptor of the foreign table.
+     <literal>inhparent</> is a boolean to show whether the relation is
+     an inheritance parent, even though foreign tables do not support table
+     inheritance right now. <literal>targetList</> is the list of
+     <literal>TargetEntry</> to be returned from the (sub-)query
+     that is currently in focus.
+    </para>
+
+    <para>
+     The result value of this function will be assigned to
+     <literal>baserel-&gt;max_attr</>, that means it is the expected number
+     of columns being fetched during the foreign table scan.
+     It should not be smaller than the number of regular columns in the definition
+     of this foreign table.  You can only return a number greater than his value to
+     acquire slots for some additional attributes, which are called
+     <firstterm>pseudo-columns</>.
+     A typical usage of a pseudo-column is to carry an identifier of
+     a particular remote row to be updated or deleted from the scanning stage
+     to the modifying stage when the foreign table is the target of
+     a data-modifying SQL statement.
+     You can return the result of the helper function
+     <literal>get_pseudo_rowid_column</> if this <literal>"rowid"</>
+     pseudo-column is the only one you need.
+   </para>
+
+   <para>
+     In addition to that, pseudo-columns can be used to off-load the burden of
+     complex calculations to foreign computing resources by replacing an
+     expression with a reference to its result, which is calculated on the
+     remote side rather than locally.
+   </para>
+
+    <para>
+<programlisting>
 void
 GetForeignRelSize (PlannerInfo *root,
                    RelOptInfo *baserel,
@@ -96,7 +144,8 @@ 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.
+     for a query involving a foreign table at the beginning of planning
+     or right after <literal>GetForeignRelWidth</>, if that callback is configured.
      <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
@@ -315,6 +364,114 @@ AcquireSampleRowsFunc (Relation relation, int elevel,
     </para>
 
     <para>
+     If a FDW supports writable foreign tables, it should implement
+     some or all of the following callback functions depending on
+     the needs and capabilities of the FDW.
+    </para>
+
+    <para>
+<programlisting>
+List *
+PlanForeignModify(PlannerInfo *root,
+                  ModifyTable *plan,
+                  Index resultRelation,
+                  Plan *subplan);
+</programlisting>
+     It allows FDW drivers to construct private information relevant to
+     the modification of the foreign table.  This private information must have
+     the form of a <literal>List *</>, which will be delivered as
+     third argument to <literal>BeginForeignModify</> during the execution stage.
+    </para>
+
+    <para>
+     <literal>root</> is the planner's global information about the query.
+     <literal>plan</> is the master plan to modify the result relation according
+     to its command type.  Please consider that <literal>ModifyTable</>
+     may have multiple result relations in a future revision, even though
+     currently there is no support for table inheritance on foreign tables.
+     <literal>resultRelation</> is an index for the result relation in the
+     range table entries, and <literal>subplan</> is the relevant scan plan;
+     that should be a <literal>ForeignScan</> for <literal>UPDATE</> or
+     <literal>DELETE</>, so the driver can access the private information of
+     the scan stage using this argument.
+    </para>
+
+    <para>
+<programlisting>
+void
+BeginForeignModify (ModifyTableState *mtstate,
+                    ResultRelInfo *resultRelInfo,
+                    List *fdw_private,
+                    Plan *subplan,
+                    int eflags);
+</programlisting>
+     It is invoked at beginning of foreign table modification, during
+     executor startup.  This routine should perform any initialization
+     needed prior to the actual table modifications, but not start
+     modifying the actual tuples. (That should be done during each call of
+     <function>ExecForeignInsert</>, <function>ExecForeignUpdate</> or
+     <function>ExecForeignDelete</>.)
+    </para>
+
+    <para>
+     The <structfield>ri_fdw_state</> field of <structname>ResultRelInfo</>
+     is reserved to store any private information relevant to this foreign table,
+     so it should be initialized to contain the per-scan state.
+     Please consider that <literal>ModifyTableState</> may have multiple
+     result relations in a future revision, even though currently there is no
+     support for table inheritance on foreign tables.  <literal>resultRelInfo</>
+     is the master information connected to this foreign table.
+     <literal>fdw_private</> is private information constructed in
+     <literal>PlanForeignModify</>, and <literal>subplan</> is the relevant
+     scan plan of this table modification.
+    </para>
+
+    <para>
+<programlisting>
+int
+ExecForeignInsert (ResultRelInfo *resultRelInfo,
+                   HeapTuple tuple);
+</programlisting>
+     Insert the given tuple into backing storage on behalf of the foreign table.
+     The supplied tuple is already formed according to the definition of
+     the relation, thus all the pseudo-columns are already filtered out.
+     It can return the number of rows being inserted, usually 1.
+    </para>
+
+    <para>
+<programlisting>
+int
+ExecForeignDelete (ResultRelInfo *resultRelInfo,
+                   const char *rowid);
+</programlisting>
+     Delete the tuple being identified with <literal>rowid</> from the backing
+     storage on behalf of the foreign table.
+     It can return the number of rows being deleted, usually 1.
+    </para>
+
+    <para>
+<programlisting>
+int
+ExecForeignUpdate (ResultRelInfo *resultRelInfo,
+                   const char *rowid,
+                   HeapTuple tuple);
+</programlisting>
+     Update the tuple being identified with <literal>rowid</> in the backing
+     storage on behalf of the foreign table with the given tuple.
+     It can return the number of rows being updated, usually 1.
+    </para>
+
+    <para>
+<programlisting>
+void
+EndForeignModify (ResultRelInfo *resultRelInfo);
+</programlisting>
+     End the modification 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>
      The <structname>FdwRoutine</> struct type is declared in
      <filename>src/include/foreign/fdwapi.h</>, which see for additional
      details.
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 0222d40..60f4c94 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"
@@ -933,6 +934,7 @@ void
 CheckValidResultRel(Relation resultRel, CmdType operation)
 {
 	TriggerDesc *trigDesc = resultRel->trigdesc;
+	FdwRoutine	*fdwroutine;
 
 	switch (resultRel->rd_rel->relkind)
 	{
@@ -991,10 +993,34 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
 			}
 			break;
 		case RELKIND_FOREIGN_TABLE:
-			ereport(ERROR,
-					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("cannot change foreign table \"%s\"",
-							RelationGetRelationName(resultRel))));
+			fdwroutine = GetFdwRoutineByRelId(RelationGetRelid(resultRel));
+			switch (operation)
+			{
+				case CMD_INSERT:
+					if (!fdwroutine || !fdwroutine->ExecForeignInsert)
+						ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("cannot insert into foreign table \"%s\"",
+									RelationGetRelationName(resultRel))));
+					break;
+				case CMD_UPDATE:
+					if (!fdwroutine || !fdwroutine->ExecForeignUpdate)
+						ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("cannot update foreign table \"%s\"",
+									RelationGetRelationName(resultRel))));
+					break;
+				case CMD_DELETE:
+					if (!fdwroutine || !fdwroutine->ExecForeignDelete)
+						ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("cannot delete from foreign table \"%s\"",
+									RelationGetRelationName(resultRel))));
+					break;
+				default:
+					elog(ERROR, "unrecognized CmdType: %d", (int) operation);
+					break;
+			}
 			break;
 		default:
 			ereport(ERROR,
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 9204859..1ee626f 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -25,6 +25,7 @@
 #include "executor/executor.h"
 #include "executor/nodeForeignscan.h"
 #include "foreign/fdwapi.h"
+#include "nodes/nodeFuncs.h"
 #include "utils/rel.h"
 
 static TupleTableSlot *ForeignNext(ForeignScanState *node);
@@ -93,6 +94,133 @@ ExecForeignScan(ForeignScanState *node)
 					(ExecScanRecheckMtd) ForeignRecheck);
 }
 
+/*
+ * pseudo_column_walker
+ *
+ * helper routine of GetPseudoTupleDesc. It pulls Var nodes that reference
+ * pseudo columns from targetlis of the relation
+ */
+typedef struct
+{
+	Relation	relation;
+	Index		varno;
+	List	   *pcolumns;
+	AttrNumber	max_attno;
+} pseudo_column_walker_context;
+
+static bool
+pseudo_column_walker(Node *node, pseudo_column_walker_context *context)
+{
+	if (node == NULL)
+		return false;
+	if (IsA(node, Var))
+	{
+		Var		   *var = (Var *) node;
+		ListCell   *cell;
+
+		if (var->varno == context->varno && var->varlevelsup == 0 &&
+			var->varattno > RelationGetNumberOfAttributes(context->relation))
+		{
+			foreach (cell, context->pcolumns)
+			{
+				Var	   *temp = lfirst(cell);
+
+				if (temp->varattno == var->varattno)
+				{
+					if (!equal(var, temp))
+						elog(ERROR, "asymmetric pseudo column appeared");
+					break;
+				}
+			}
+			if (!cell)
+			{
+				context->pcolumns = lappend(context->pcolumns, var);
+				if (var->varattno > context->max_attno)
+					context->max_attno = var->varattno;
+			}
+		}
+		return false;
+	}
+
+	/* Should not find an unplanned subquery */
+	Assert(!IsA(node, Query));
+
+	return expression_tree_walker(node, pseudo_column_walker,
+								  (void *)context);
+}
+
+/*
+ * GetPseudoTupleDesc
+ *
+ * It generates TupleDesc structure including pseudo-columns if required.
+ */
+static TupleDesc
+GetPseudoTupleDesc(ForeignScan *node, Relation relation)
+{
+	pseudo_column_walker_context context;
+	List	   *target_list = node->scan.plan.targetlist;
+	TupleDesc	tupdesc;
+	AttrNumber	attno;
+	ListCell   *cell;
+	ListCell   *prev;
+	bool		hasoid;
+
+	context.relation = relation;
+	context.varno = node->scan.scanrelid;
+	context.pcolumns = NIL;
+	context.max_attno = -1;
+
+	pseudo_column_walker((Node *)target_list, (void *)&context);
+	Assert(context.max_attno > RelationGetNumberOfAttributes(relation));
+
+	hasoid = RelationGetForm(relation)->relhasoids;
+	tupdesc = CreateTemplateTupleDesc(context.max_attno, hasoid);
+
+	for (attno = 1; attno <= context.max_attno; attno++)
+	{
+		/* case of regular columns */
+		if (attno <= RelationGetNumberOfAttributes(relation))
+		{
+			memcpy(tupdesc->attrs[attno - 1],
+				   RelationGetDescr(relation)->attrs[attno - 1],
+				   ATTRIBUTE_FIXED_PART_SIZE);
+			continue;
+		}
+
+		/* case of pseudo columns */
+		prev = NULL;
+		foreach (cell, context.pcolumns)
+		{
+			Var	   *var = lfirst(cell);
+
+			if (var->varattno == attno)
+			{
+				char		namebuf[NAMEDATALEN];
+
+				snprintf(namebuf, sizeof(namebuf),
+						 "pseudo_column_%d", attno);
+
+				TupleDescInitEntry(tupdesc,
+								   attno,
+								   namebuf,
+								   var->vartype,
+								   var->vartypmod,
+								   0);
+				TupleDescInitEntryCollation(tupdesc,
+											attno,
+											var->varcollid);
+				context.pcolumns
+					= list_delete_cell(context.pcolumns, cell, prev);
+				break;
+			}
+			prev = cell;
+		}
+		if (!cell)
+			elog(ERROR, "pseudo column %d of %s not in target list",
+				 attno, RelationGetRelationName(relation));
+	}
+	return tupdesc;
+}
 
 /* ----------------------------------------------------------------
  *		ExecInitForeignScan
@@ -103,6 +231,7 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
 {
 	ForeignScanState *scanstate;
 	Relation	currentRelation;
+	TupleDesc	tupdesc;
 	FdwRoutine *fdwroutine;
 
 	/* check for unsupported flags */
@@ -149,7 +278,12 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
 	/*
 	 * get the scan type from the relation descriptor.
 	 */
-	ExecAssignScanType(&scanstate->ss, RelationGetDescr(currentRelation));
+	if (node->fsPseudoCol)
+		tupdesc = GetPseudoTupleDesc(node, currentRelation);
+	else
+		tupdesc = RelationGetDescr(currentRelation);
+
+	ExecAssignScanType(&scanstate->ss, tupdesc);
 
 	/*
 	 * Initialize result tuple type and projection info.
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d31015c..6c2b6e5 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"
@@ -170,6 +171,7 @@ ExecInsert(TupleTableSlot *slot,
 	Relation	resultRelationDesc;
 	Oid			newId;
 	List	   *recheckIndexes = NIL;
+	int			num_rows = 1;
 
 	/*
 	 * get the heap tuple out of the tuple table slot, making sure we have a
@@ -225,6 +227,14 @@ ExecInsert(TupleTableSlot *slot,
 
 		newId = InvalidOid;
 	}
+	else if (resultRelInfo->ri_fdwroutine)
+	{
+		FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+		num_rows = fdwroutine->ExecForeignInsert(resultRelInfo, tuple);
+
+		newId = InvalidOid;
+	}
 	else
 	{
 		/*
@@ -252,7 +262,7 @@ ExecInsert(TupleTableSlot *slot,
 
 	if (canSetTag)
 	{
-		(estate->es_processed)++;
+		(estate->es_processed) += num_rows;
 		estate->es_lastoid = newId;
 		setLastTid(&(tuple->t_self));
 	}
@@ -285,7 +295,7 @@ ExecInsert(TupleTableSlot *slot,
  * ----------------------------------------------------------------
  */
 static TupleTableSlot *
-ExecDelete(ItemPointer tupleid,
+ExecDelete(Datum rowid,
 		   HeapTupleHeader oldtuple,
 		   TupleTableSlot *planSlot,
 		   EPQState *epqstate,
@@ -296,6 +306,7 @@ ExecDelete(ItemPointer tupleid,
 	Relation	resultRelationDesc;
 	HTSU_Result result;
 	HeapUpdateFailureData hufd;
+	int			num_rows = 1;
 
 	/*
 	 * get information on the (current) result relation
@@ -310,7 +321,7 @@ ExecDelete(ItemPointer tupleid,
 		bool		dodelete;
 
 		dodelete = ExecBRDeleteTriggers(estate, epqstate, resultRelInfo,
-										tupleid);
+										(ItemPointer)DatumGetPointer(rowid));
 
 		if (!dodelete)			/* "do nothing" */
 			return NULL;
@@ -334,8 +345,17 @@ ExecDelete(ItemPointer tupleid,
 		if (!dodelete)			/* "do nothing" */
 			return NULL;
 	}
+	else if (resultRelInfo->ri_fdwroutine)
+	{
+		FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+		num_rows = fdwroutine->ExecForeignDelete(resultRelInfo,
+												 DatumGetCString(rowid));
+	}
 	else
 	{
+		ItemPointer	tupleid = (ItemPointer) DatumGetPointer(rowid);
+
 		/*
 		 * delete the tuple
 		 *
@@ -430,10 +450,11 @@ ldelete:;
 	}
 
 	if (canSetTag)
-		(estate->es_processed)++;
+		(estate->es_processed) += num_rows;
 
 	/* AFTER ROW DELETE Triggers */
-	ExecARDeleteTriggers(estate, resultRelInfo, tupleid);
+	ExecARDeleteTriggers(estate, resultRelInfo,
+						 (ItemPointer)DatumGetPointer(rowid));
 
 	/* Process RETURNING if present */
 	if (resultRelInfo->ri_projectReturning)
@@ -457,7 +478,8 @@ ldelete:;
 		}
 		else
 		{
-			deltuple.t_self = *tupleid;
+			ItemPointerCopy((ItemPointer)DatumGetPointer(rowid),
+							&deltuple.t_self);
 			if (!heap_fetch(resultRelationDesc, SnapshotAny,
 							&deltuple, &delbuffer, false, NULL))
 				elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
@@ -499,7 +521,7 @@ ldelete:;
  * ----------------------------------------------------------------
  */
 static TupleTableSlot *
-ExecUpdate(ItemPointer tupleid,
+ExecUpdate(Datum rowid,
 		   HeapTupleHeader oldtuple,
 		   TupleTableSlot *slot,
 		   TupleTableSlot *planSlot,
@@ -513,6 +535,7 @@ ExecUpdate(ItemPointer tupleid,
 	HTSU_Result result;
 	HeapUpdateFailureData hufd;
 	List	   *recheckIndexes = NIL;
+	int			num_rows = 1;
 
 	/*
 	 * abort the operation if not running transactions
@@ -537,7 +560,7 @@ ExecUpdate(ItemPointer tupleid,
 		resultRelInfo->ri_TrigDesc->trig_update_before_row)
 	{
 		slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
-									tupleid, slot);
+									(ItemPointer)DatumGetPointer(rowid), slot);
 
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
@@ -567,8 +590,18 @@ ExecUpdate(ItemPointer tupleid,
 		/* trigger might have changed tuple */
 		tuple = ExecMaterializeSlot(slot);
 	}
+	else if (resultRelInfo->ri_fdwroutine)
+	{
+		FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+		num_rows = fdwroutine->ExecForeignUpdate(resultRelInfo,
+												 DatumGetCString(rowid),
+												 tuple);
+	}
 	else
 	{
+		ItemPointer	tupleid = (ItemPointer) DatumGetPointer(rowid);
+
 		/*
 		 * Check the constraints of the tuple
 		 *
@@ -687,11 +720,12 @@ lreplace:;
 	}
 
 	if (canSetTag)
-		(estate->es_processed)++;
+		(estate->es_processed) += num_rows;
 
 	/* AFTER ROW UPDATE Triggers */
-	ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple,
-						 recheckIndexes);
+	ExecARUpdateTriggers(estate, resultRelInfo,
+						 (ItemPointer) DatumGetPointer(rowid),
+						 tuple, recheckIndexes);
 
 	list_free(recheckIndexes);
 
@@ -771,6 +805,7 @@ ExecModifyTable(ModifyTableState *node)
 	TupleTableSlot *planSlot;
 	ItemPointer tupleid = NULL;
 	ItemPointerData tuple_ctid;
+	Datum		rowid = 0;
 	HeapTupleHeader oldtuple = NULL;
 
 	/*
@@ -859,17 +894,19 @@ ExecModifyTable(ModifyTableState *node)
 		if (junkfilter != NULL)
 		{
 			/*
-			 * extract the 'ctid' or 'wholerow' junk attribute.
+			 * extract the 'ctid', 'rowid' or 'wholerow' junk attribute.
 			 */
 			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,
+												 junkfilter->jf_junkRowidNo,
 												 &isNull);
 					/* shouldn't ever get a null result... */
 					if (isNull)
@@ -877,13 +914,33 @@ ExecModifyTable(ModifyTableState *node)
 
 					tupleid = (ItemPointer) DatumGetPointer(datum);
 					tuple_ctid = *tupleid;		/* be sure we don't free
-												 * ctid!! */
-					tupleid = &tuple_ctid;
+												 * ctid ! */
+					rowid = PointerGetDatum(&tuple_ctid);
+				}
+				else if (relkind == RELKIND_FOREIGN_TABLE)
+				{
+					datum = ExecGetJunkAttribute(slot,
+												 junkfilter->jf_junkRowidNo,
+												 &isNull);
+					/* shouldn't ever get a null result... */
+					if (isNull)
+						elog(ERROR, "rowid is NULL");
+
+					rowid = datum;
+
+					datum = ExecGetJunkAttribute(slot,
+												 junkfilter->jf_junkRecordNo,
+												 &isNull);
+					/* shouldn't ever get a null result... */
+					if (isNull)
+						elog(ERROR, "wholerow is NULL");
+
+					oldtuple = DatumGetHeapTupleHeader(datum);
 				}
 				else
 				{
 					datum = ExecGetJunkAttribute(slot,
-												 junkfilter->jf_junkAttNo,
+												 junkfilter->jf_junkRecordNo,
 												 &isNull);
 					/* shouldn't ever get a null result... */
 					if (isNull)
@@ -906,11 +963,11 @@ ExecModifyTable(ModifyTableState *node)
 				slot = ExecInsert(slot, planSlot, estate, node->canSetTag);
 				break;
 			case CMD_UPDATE:
-				slot = ExecUpdate(tupleid, oldtuple, slot, planSlot,
+				slot = ExecUpdate(rowid, oldtuple, slot, planSlot,
 								&node->mt_epqstate, estate, node->canSetTag);
 				break;
 			case CMD_DELETE:
-				slot = ExecDelete(tupleid, oldtuple, planSlot,
+				slot = ExecDelete(rowid, oldtuple, planSlot,
 								&node->mt_epqstate, estate, node->canSetTag);
 				break;
 			default:
@@ -997,6 +1054,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	i = 0;
 	foreach(l, node->plans)
 	{
+		char	relkind;
+
 		subplan = (Plan *) lfirst(l);
 
 		/*
@@ -1022,6 +1081,24 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		estate->es_result_relation_info = resultRelInfo;
 		mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
 
+		/*
+		 * Also tells FDW extensions to init the plan for this result rel
+		 */
+		relkind = RelationGetForm(resultRelInfo->ri_RelationDesc)->relkind;
+		if (relkind == RELKIND_FOREIGN_TABLE)
+		{
+			Oid		relid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+			FdwRoutine *fdwroutine = GetFdwRoutineByRelId(relid);
+			List   *fdwprivate = list_nth(node->fdwPrivList, i);
+
+			Assert(fdwroutine != NULL);
+			resultRelInfo->ri_fdwroutine = fdwroutine;
+			resultRelInfo->ri_fdw_state = NULL;
+
+			if (fdwroutine->BeginForeignModify)
+				fdwroutine->BeginForeignModify(mtstate, resultRelInfo,
+											   fdwprivate, subplan, eflags);
+		}
 		resultRelInfo++;
 		i++;
 	}
@@ -1163,6 +1240,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			for (i = 0; i < nplans; i++)
 			{
 				JunkFilter *j;
+				char		relkind =
+				    RelationGetForm(resultRelInfo->ri_RelationDesc)->relkind;
 
 				subplan = mtstate->mt_plans[i]->plan;
 				if (operation == CMD_INSERT || operation == CMD_UPDATE)
@@ -1176,16 +1255,27 @@ 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)
+					if (relkind == RELKIND_RELATION)
 					{
-						j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
-						if (!AttributeNumberIsValid(j->jf_junkAttNo))
+						j->jf_junkRowidNo = ExecFindJunkAttribute(j, "ctid");
+						if (!AttributeNumberIsValid(j->jf_junkRowidNo))
 							elog(ERROR, "could not find junk ctid column");
 					}
+					else if (relkind == RELKIND_FOREIGN_TABLE)
+					{
+						j->jf_junkRowidNo = ExecFindJunkAttribute(j, "rowid");
+						if (!AttributeNumberIsValid(j->jf_junkRowidNo))
+							elog(ERROR, "could not find junk rowid column");
+						j->jf_junkRecordNo
+							= ExecFindJunkAttribute(j, "record");
+						if (!AttributeNumberIsValid(j->jf_junkRecordNo))
+							elog(ERROR, "could not find junk wholerow column");
+					}
 					else
 					{
-						j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
-						if (!AttributeNumberIsValid(j->jf_junkAttNo))
+						j->jf_junkRecordNo
+							= ExecFindJunkAttribute(j, "wholerow");
+						if (!AttributeNumberIsValid(j->jf_junkRecordNo))
 							elog(ERROR, "could not find junk wholerow column");
 					}
 				}
@@ -1239,6 +1329,21 @@ ExecEndModifyTable(ModifyTableState *node)
 {
 	int			i;
 
+	/* Let the FDW shut dowm */
+	for (i=0; i < node->ps.state->es_num_result_relations; i++)
+	{
+		ResultRelInfo  *resultRelInfo
+			= &node->ps.state->es_result_relations[i];
+
+		if (resultRelInfo->ri_fdwroutine)
+		{
+			FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+			if (fdwroutine->EndForeignModify)
+				fdwroutine->EndForeignModify(resultRelInfo);
+		}
+	}
+
 	/*
 	 * Free the exprcontext
 	 */
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
index d8845aa..4363a28 100644
--- a/src/backend/foreign/foreign.c
+++ b/src/backend/foreign/foreign.c
@@ -571,3 +571,71 @@ get_foreign_server_oid(const char *servername, bool missing_ok)
 				 errmsg("server \"%s\" does not exist", servername)));
 	return oid;
 }
+
+/*
+ * get_pseudo_rowid_column
+ *
+ * It picks up an attribute number to be used for the pseudo rowid column,
+ * if it exists.  It should be injected at rewriteHandler.c if the supplied query
+ * is a UPDATE or DELETE command.  Elsewhere, it returns InvalidAttrNumber.
+ */
+AttrNumber
+get_pseudo_rowid_column(RelOptInfo *baserel, List *targetList)
+{
+	ListCell   *cell;
+
+	foreach (cell, targetList)
+	{
+		TargetEntry *tle = lfirst(cell);
+
+		if (tle->resjunk &&
+			tle->resname && strcmp(tle->resname, "rowid") == 0)
+		{
+			Var	   *var;
+
+			if (!IsA(tle->expr, Var))
+				elog(ERROR, "unexpected node on junk rowid entry: %d",
+					 (int) nodeTag(tle->expr));
+
+			var = (Var *) tle->expr;
+			if (baserel->relid == var->varno)
+				return var->varattno;
+		}
+	}
+	return InvalidAttrNumber;
+}
+
+/*
+ * lookup_foreign_scan_plan
+ *
+ * It looks up the ForeignScan plan node with the supplied range-table id.
+ * Its typical usage is for PlanForeignModify to get the underlying scan plan on
+ * UPDATE or DELETE commands.
+ */
+ForeignScan *
+lookup_foreign_scan_plan(Plan *subplan, Index rtindex)
+{
+	if (!subplan)
+		return NULL;
+
+	else if (IsA(subplan, ForeignScan))
+	{
+		ForeignScan	   *fscan = (ForeignScan *) subplan;
+
+		if (fscan->scan.scanrelid == rtindex)
+			return fscan;
+	}
+	else
+	{
+		ForeignScan    *fscan;
+
+		fscan = lookup_foreign_scan_plan(subplan->lefttree, rtindex);
+		if (fscan != NULL)
+			return fscan;
+
+		fscan = lookup_foreign_scan_plan(subplan->righttree, rtindex);
+		if (fscan != NULL)
+			return fscan;
+	}
+	return NULL;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 9387ee9..5b1e8ac 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -181,6 +181,7 @@ _copyModifyTable(const ModifyTable *from)
 	COPY_NODE_FIELD(returningLists);
 	COPY_NODE_FIELD(rowMarks);
 	COPY_SCALAR_FIELD(epqParam);
+	COPY_NODE_FIELD(fdwPrivList);
 
 	return newnode;
 }
@@ -594,6 +595,7 @@ _copyForeignScan(const ForeignScan *from)
 	COPY_NODE_FIELD(fdw_exprs);
 	COPY_NODE_FIELD(fdw_private);
 	COPY_SCALAR_FIELD(fsSystemCol);
+	COPY_SCALAR_FIELD(fsPseudoCol);
 
 	return newnode;
 }
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 35c6287..c32ebd3 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -335,6 +335,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
 	WRITE_NODE_FIELD(returningLists);
 	WRITE_NODE_FIELD(rowMarks);
 	WRITE_INT_FIELD(epqParam);
+	WRITE_NODE_FIELD(fdwPrivList);
 }
 
 static void
@@ -562,6 +563,7 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
 	WRITE_NODE_FIELD(fdw_exprs);
 	WRITE_NODE_FIELD(fdw_private);
 	WRITE_BOOL_FIELD(fsSystemCol);
+	WRITE_BOOL_FIELD(fsPseudoCol);
 }
 
 static void
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 030f420..91db1b8 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -39,6 +39,7 @@
 #include "parser/parse_clause.h"
 #include "parser/parsetree.h"
 #include "utils/lsyscache.h"
+#include "utils/rel.h"
 
 
 static Plan *create_plan_recurse(PlannerInfo *root, Path *best_path);
@@ -1943,6 +1944,8 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 	RelOptInfo *rel = best_path->path.parent;
 	Index		scan_relid = rel->relid;
 	RangeTblEntry *rte;
+	Relation	relation;
+	AttrNumber	num_attrs;
 	int			i;
 
 	/* it should be a base rel... */
@@ -2001,6 +2004,22 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 		}
 	}
 
+	/*
+	 * Also, detect whether any pseudo columns are requested from rel.
+	 */
+	relation = heap_open(rte->relid, NoLock);
+	scan_plan->fsPseudoCol = false;
+	num_attrs = RelationGetNumberOfAttributes(relation);
+	for (i = num_attrs + 1; i <= rel->max_attr; i++)
+	{
+		if (!bms_is_empty(rel->attr_needed[i - rel->min_attr]))
+		{
+			scan_plan->fsPseudoCol = true;
+			break;
+		}
+	}
+	heap_close(relation, NoLock);
+
 	return scan_plan;
 }
 
@@ -4695,7 +4714,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)
@@ -4704,6 +4724,8 @@ make_modifytable(CmdType operation, bool canSetTag,
 	Plan	   *plan = &node->plan;
 	double		total_size;
 	ListCell   *subnode;
+	ListCell   *resultRel;
+	List	   *fdw_priv_list = NIL;
 
 	Assert(list_length(resultRelations) == list_length(subplans));
 	Assert(returningLists == NIL ||
@@ -4746,6 +4768,30 @@ make_modifytable(CmdType operation, bool canSetTag,
 	node->rowMarks = rowMarks;
 	node->epqParam = epqParam;
 
+	/*
+	 * Allow FDW driver to construct its private plan if the result relation
+	 * is a foreign table.
+	 */
+	forboth (resultRel, resultRelations, subnode, subplans)
+	{
+		RangeTblEntry  *rte = rt_fetch(lfirst_int(resultRel),
+									   root->parse->rtable);
+		List		   *fdw_private = NIL;
+		char			relkind = get_rel_relkind(rte->relid);
+
+		if (relkind == RELKIND_FOREIGN_TABLE)
+		{
+			FdwRoutine *fdwroutine = GetFdwRoutineByRelId(rte->relid);
+
+			if (fdwroutine && fdwroutine->PlanForeignModify)
+				fdw_private = fdwroutine->PlanForeignModify(root, node,
+													lfirst_int(resultRel),
+													(Plan *) lfirst(subnode));
+		}
+		fdw_priv_list = lappend(fdw_priv_list, fdw_private);
+	}
+	node->fdwPrivList = fdw_priv_list;
+
 	return node;
 }
 
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index bd719b5..57ba192 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -85,7 +85,7 @@ static void check_hashjoinable(RestrictInfo *restrictinfo);
  * "other rel" RelOptInfos for the members of any appendrels we find here.)
  */
 void
-add_base_rels_to_query(PlannerInfo *root, Node *jtnode)
+add_base_rels_to_query(PlannerInfo *root, List *tlist, Node *jtnode)
 {
 	if (jtnode == NULL)
 		return;
@@ -93,7 +93,7 @@ add_base_rels_to_query(PlannerInfo *root, Node *jtnode)
 	{
 		int			varno = ((RangeTblRef *) jtnode)->rtindex;
 
-		(void) build_simple_rel(root, varno, RELOPT_BASEREL);
+		(void) build_simple_rel(root, varno, tlist, RELOPT_BASEREL);
 	}
 	else if (IsA(jtnode, FromExpr))
 	{
@@ -101,14 +101,14 @@ add_base_rels_to_query(PlannerInfo *root, Node *jtnode)
 		ListCell   *l;
 
 		foreach(l, f->fromlist)
-			add_base_rels_to_query(root, lfirst(l));
+			add_base_rels_to_query(root, tlist, lfirst(l));
 	}
 	else if (IsA(jtnode, JoinExpr))
 	{
 		JoinExpr   *j = (JoinExpr *) jtnode;
 
-		add_base_rels_to_query(root, j->larg);
-		add_base_rels_to_query(root, j->rarg);
+		add_base_rels_to_query(root, tlist, j->larg);
+		add_base_rels_to_query(root, tlist, j->rarg);
 	}
 	else
 		elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
index c2488a4..4442444 100644
--- a/src/backend/optimizer/plan/planmain.c
+++ b/src/backend/optimizer/plan/planmain.c
@@ -112,6 +112,9 @@ query_planner(PlannerInfo *root, List *tlist,
 	 */
 	if (parse->jointree->fromlist == NIL)
 	{
+		/* Make a flattened version of the rangetable for faster access */
+		setup_simple_rel_arrays(root);
+
 		/* We need a trivial path result */
 		*cheapest_path = (Path *)
 			create_result_path((List *) parse->jointree->quals);
@@ -163,7 +166,7 @@ query_planner(PlannerInfo *root, List *tlist,
 	 * rangetable may contain RTEs for rels not actively part of the query,
 	 * for example views.  We don't want to make RelOptInfos for them.
 	 */
-	add_base_rels_to_query(root, (Node *) parse->jointree);
+	add_base_rels_to_query(root, tlist, (Node *) parse->jointree);
 
 	/*
 	 * Examine the targetlist and join tree, adding entries to baserel
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b61005f..2d72244 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,
@@ -3385,7 +3387,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 	setup_simple_rel_arrays(root);
 
 	/* Build RelOptInfo */
-	rel = build_simple_rel(root, 1, RELOPT_BASEREL);
+	rel = build_simple_rel(root, 1, NIL, RELOPT_BASEREL);
 
 	/* Locate IndexOptInfo for the target index */
 	indexInfo = NULL;
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index b91e9f4..61bc447 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -236,7 +236,8 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 		 * used for anything here, but it carries the subroot data structures
 		 * forward to setrefs.c processing.
 		 */
-		rel = build_simple_rel(root, rtr->rtindex, RELOPT_BASEREL);
+		rel = build_simple_rel(root, rtr->rtindex, refnames_tlist,
+							   RELOPT_BASEREL);
 
 		/* plan_params should not be in use in current query level */
 		Assert(root->plan_params == NIL);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 04d5028..7d6bc4b 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -25,6 +25,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/heap.h"
+#include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/clauses.h"
@@ -80,7 +81,7 @@ static List *build_index_tlist(PlannerInfo *root, IndexOptInfo *index,
  */
 void
 get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
-				  RelOptInfo *rel)
+				  List *tlist, RelOptInfo *rel)
 {
 	Index		varno = rel->relid;
 	Relation	relation;
@@ -104,6 +105,30 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 	rel->max_attr = RelationGetNumberOfAttributes(relation);
 	rel->reltablespace = RelationGetForm(relation)->reltablespace;
 
+	/*
+	 * Adjust width of attr_needed slot in case FDW extension wants
+	 * to return pseudo-columns in addition to the columns in its
+	 * table definition.
+	 * GetForeignRelWidth, an optional FDW handler, enables a FDW
+	 * to save properties of a pseudo-column in its private field.
+	 * When the foreign table is the target of UPDATE/DELETE, the query rewriter
+	 * injects a "rowid" pseudo-column to track the remote row to be modified,
+	 * so the FDW has to track which varattno shall perform as "rowid".
+	 */
+	if (RelationGetForm(relation)->relkind == RELKIND_FOREIGN_TABLE)
+	{
+		FdwRoutine *fdwroutine = GetFdwRoutineByRelId(relationObjectId);
+
+		if (fdwroutine->GetForeignRelWidth)
+		{
+			rel->max_attr = fdwroutine->GetForeignRelWidth(root, rel,
+														   relation,
+														   inhparent,
+														   tlist);
+			Assert(rel->max_attr >= RelationGetNumberOfAttributes(relation));
+		}
+	}
+
 	Assert(rel->max_attr >= rel->min_attr);
 	rel->attr_needed = (Relids *)
 		palloc0((rel->max_attr - rel->min_attr + 1) * sizeof(Relids));
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index f724714..84b674c 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -80,7 +80,8 @@ setup_simple_rel_arrays(PlannerInfo *root)
  *	  Construct a new RelOptInfo for a base relation or 'other' relation.
  */
 RelOptInfo *
-build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
+build_simple_rel(PlannerInfo *root, int relid, List *tlist,
+				 RelOptKind reloptkind)
 {
 	RelOptInfo *rel;
 	RangeTblEntry *rte;
@@ -133,7 +134,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
 	{
 		case RTE_RELATION:
 			/* Table --- retrieve statistics from the system catalogs */
-			get_relation_info(root, rte->relid, rte->inh, rel);
+			get_relation_info(root, rte->relid, rte->inh, tlist, rel);
 			break;
 		case RTE_SUBQUERY:
 		case RTE_FUNCTION:
@@ -180,7 +181,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
 			if (appinfo->parent_relid != relid)
 				continue;
 
-			(void) build_simple_rel(root, appinfo->child_relid,
+			(void) build_simple_rel(root, appinfo->child_relid, tlist,
 									RELOPT_OTHER_MEMBER_REL);
 		}
 	}
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 36d95eb..7d4bb99 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -2087,7 +2087,20 @@ get_rte_attribute_name(RangeTblEntry *rte, AttrNumber attnum)
 	 * built (which can easily happen for rules).
 	 */
 	if (rte->rtekind == RTE_RELATION)
-		return get_relid_attribute_name(rte->relid, attnum);
+	{
+		char   *attname = get_attname(rte->relid, attnum);
+
+		if (attname)
+			return attname;
+
+		/*
+		 * XXX - If FDW driver adds pseudo-columns, it may have attribute
+		 * number larger than number of relation's attribute. In this case,
+		 * get_attname() returns NULL and we fall back on alias list on eref.
+		 * It should not happen other than foreign tables.
+		 */
+		Assert(get_rel_relkind(rte->relid) == RELKIND_FOREIGN_TABLE);
+	}
 
 	/*
 	 * Otherwise use the column name from eref.  There should always be one.
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 990ca34..2aa46ee 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1165,7 +1165,10 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 					Relation target_relation)
 {
 	Var		   *var;
-	const char *attrname;
+	List	   *varList;
+	List	   *attNameList;
+	ListCell   *cell1;
+	ListCell   *cell2;
 	TargetEntry *tle;
 
 	if (target_relation->rd_rel->relkind == RELKIND_RELATION)
@@ -1179,8 +1182,35 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 					  -1,
 					  InvalidOid,
 					  0);
+		varList = list_make1(var);
+		attNameList = list_make1("ctid");
+	}
+	else if (target_relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+	{
+		/*
+		 * Emit Rowid so that executor can find the row to update or delete.
+		 */
+		var = makeVar(parsetree->resultRelation,
+					  RelationGetNumberOfAttributes(target_relation) + 1,
+					  CSTRINGOID,
+					  -2,
+					  InvalidOid,
+					  0);
+		varList = list_make1(var);
+
+		/*
+		 * Emit generic record Var so that executor will have the "old" view
+		 * row to pass the RETURNING clause (or upcoming triggers).
+		 */
+		var = makeVar(parsetree->resultRelation,
+					  InvalidAttrNumber,
+					  RECORDOID,
+					  -1,
+					  InvalidOid,
+					  0);
+		varList = lappend(varList, var);
 
-		attrname = "ctid";
+		attNameList = list_make2("rowid", "record");
 	}
 	else
 	{
@@ -1192,16 +1222,21 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 							  parsetree->resultRelation,
 							  0,
 							  false);
-
-		attrname = "wholerow";
+		varList = list_make1(var);
+		attNameList = list_make1("wholerow");
 	}
 
-	tle = makeTargetEntry((Expr *) var,
-						  list_length(parsetree->targetList) + 1,
-						  pstrdup(attrname),
-						  true);
-
-	parsetree->targetList = lappend(parsetree->targetList, tle);
+	/*
+	 * Append them to targetList
+	 */
+	forboth (cell1, varList, cell2, attNameList)
+	{
+		tle = makeTargetEntry((Expr *)lfirst(cell1),
+							  list_length(parsetree->targetList) + 1,
+							  pstrdup((const char *)lfirst(cell2)),
+							  true);
+		parsetree->targetList = lappend(parsetree->targetList, tle);
+	}
 }
 
 
diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
index 721cd25..fa3ffb7 100644
--- a/src/include/foreign/fdwapi.h
+++ b/src/include/foreign/fdwapi.h
@@ -14,6 +14,7 @@
 
 #include "nodes/execnodes.h"
 #include "nodes/relation.h"
+#include "utils/rel.h"
 
 /* To avoid including explain.h here, reference ExplainState thus: */
 struct ExplainState;
@@ -22,6 +23,11 @@ struct ExplainState;
 /*
  * Callback function signatures --- see fdwhandler.sgml for more info.
  */
+typedef AttrNumber (*GetForeignRelWidth_function) (PlannerInfo *root,
+												   RelOptInfo *baserel,
+												   Relation foreignrel,
+												   bool inhparent,
+												   List *targetList);
 
 typedef void (*GetForeignRelSize_function) (PlannerInfo *root,
 														RelOptInfo *baserel,
@@ -58,6 +64,24 @@ typedef int (*AcquireSampleRowsFunc) (Relation relation, int elevel,
 typedef bool (*AnalyzeForeignTable_function) (Relation relation,
 												 AcquireSampleRowsFunc *func,
 													BlockNumber *totalpages);
+typedef List *(*PlanForeignModify_function) (PlannerInfo *root,
+											 ModifyTable *plan,
+											 Index resultRelation,
+											 Plan *subplan);
+
+typedef void (*BeginForeignModify_function) (ModifyTableState *mtstate,
+											 ResultRelInfo *resultRelInfo,
+											 List *fdw_private,
+											 Plan *subplan,
+											 int eflags);
+typedef int	(*ExecForeignInsert_function) (ResultRelInfo *resultRelInfo,
+										   HeapTuple tuple);
+typedef int	(*ExecForeignDelete_function) (ResultRelInfo *resultRelInfo,
+										   const char *rowid);
+typedef int	(*ExecForeignUpdate_function) (ResultRelInfo *resultRelInfo,
+										   const char *rowid,
+										   HeapTuple tuple);
+typedef void (*EndForeignModify_function) (ResultRelInfo *resultRelInfo);
 
 /*
  * FdwRoutine is the struct returned by a foreign-data wrapper's handler
@@ -90,6 +114,13 @@ typedef struct FdwRoutine
 	 * not provided.
 	 */
 	AnalyzeForeignTable_function AnalyzeForeignTable;
+	GetForeignRelWidth_function GetForeignRelWidth;
+	PlanForeignModify_function PlanForeignModify;
+	BeginForeignModify_function	BeginForeignModify;
+	ExecForeignInsert_function ExecForeignInsert;
+	ExecForeignDelete_function ExecForeignDelete;
+	ExecForeignUpdate_function ExecForeignUpdate;
+	EndForeignModify_function EndForeignModify;
 } FdwRoutine;
 
 
diff --git a/src/include/foreign/foreign.h b/src/include/foreign/foreign.h
index f8aa99e..b63000a 100644
--- a/src/include/foreign/foreign.h
+++ b/src/include/foreign/foreign.h
@@ -14,6 +14,8 @@
 #define FOREIGN_H
 
 #include "nodes/parsenodes.h"
+#include "nodes/plannodes.h"
+#include "nodes/relation.h"
 
 
 /* Helper for obtaining username for user mapping */
@@ -81,4 +83,8 @@ extern List *GetForeignColumnOptions(Oid relid, AttrNumber attnum);
 extern Oid	get_foreign_data_wrapper_oid(const char *fdwname, bool missing_ok);
 extern Oid	get_foreign_server_oid(const char *servername, bool missing_ok);
 
+extern AttrNumber get_pseudo_rowid_column(RelOptInfo *baserel,
+										  List *targetList);
+extern ForeignScan *lookup_foreign_scan_plan(Plan *subplan,
+											 Index rtindex);
 #endif   /* FOREIGN_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index d4911bd..18ab231 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -268,9 +268,11 @@ typedef struct ProjectionInfo
  *						attribute numbers of the "original" tuple and the
  *						attribute numbers of the "clean" tuple.
  *	  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
+ *	  jf_junkRowidNo:	not used by junkfilter code.  Can be used by caller
+ *						to remember the attno used to track a particular tuple
+ *						being updated or deleted.
  *						(execMain.c stores the "ctid" attno here).
+ *	  jf_junkRecordNo:	Also, the attno of whole-row reference.
  * ----------------
  */
 typedef struct JunkFilter
@@ -280,7 +282,8 @@ typedef struct JunkFilter
 	TupleDesc	jf_cleanTupType;
 	AttrNumber *jf_cleanMap;
 	TupleTableSlot *jf_resultSlot;
-	AttrNumber	jf_junkAttNo;
+	AttrNumber	jf_junkRowidNo;
+	AttrNumber	jf_junkRecordNo;
 } JunkFilter;
 
 /* ----------------
@@ -303,6 +306,8 @@ typedef struct JunkFilter
  *		ConstraintExprs			array of constraint-checking expr states
  *		junkFilter				for removing junk attributes from tuples
  *		projectReturning		for computing a RETURNING list
+ *		fdwroutine				FDW callbacks if foreign table
+ *		fdw_state				opaque state of FDW module, or NULL
  * ----------------
  */
 typedef struct ResultRelInfo
@@ -320,6 +325,8 @@ typedef struct ResultRelInfo
 	List	  **ri_ConstraintExprs;
 	JunkFilter *ri_junkFilter;
 	ProjectionInfo *ri_projectReturning;
+	struct FdwRoutine  *ri_fdwroutine;
+	void	   *ri_fdw_state;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index fb9a863..93481aa 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -175,6 +175,7 @@ typedef struct ModifyTable
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
 	int			epqParam;		/* ID of Param for EvalPlanQual re-eval */
+	List	   *fdwPrivList;	/* private fields for foreign tables */
 } ModifyTable;
 
 /* ----------------
@@ -478,6 +479,7 @@ typedef struct ForeignScan
 	List	   *fdw_exprs;		/* expressions that FDW may evaluate */
 	List	   *fdw_private;	/* private data for FDW */
 	bool		fsSystemCol;	/* true if any "system column" is needed */
+	bool		fsPseudoCol;	/* true if any "pseudo column" is needed */
 } ForeignScan;
 
 
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index de889fb..adfc93d 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -133,7 +133,7 @@ extern Path *reparameterize_path(PlannerInfo *root, Path *path,
  */
 extern void setup_simple_rel_arrays(PlannerInfo *root);
 extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid,
-				 RelOptKind reloptkind);
+			   List *tlist, RelOptKind reloptkind);
 extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid);
 extern RelOptInfo *find_join_rel(PlannerInfo *root, Relids relids);
 extern RelOptInfo *build_join_rel(PlannerInfo *root,
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index e0d04db..c5fce41 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -26,7 +26,7 @@ extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook;
 
 
 extern void get_relation_info(PlannerInfo *root, Oid relationObjectId,
-				  bool inhparent, RelOptInfo *rel);
+				  bool inhparent, List *tlist, RelOptInfo *rel);
 
 extern void estimate_rel_size(Relation rel, int32 *attr_widths,
 				  BlockNumber *pages, double *tuples, double *allvisfrac);
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 0fe696c..746a603 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);
@@ -90,7 +91,8 @@ extern bool is_projection_capable_plan(Plan *plan);
 extern int	from_collapse_limit;
 extern int	join_collapse_limit;
 
-extern void add_base_rels_to_query(PlannerInfo *root, Node *jtnode);
+extern void add_base_rels_to_query(PlannerInfo *root, List *tlist,
+								   Node *jtnode);
 extern void build_base_rel_tlists(PlannerInfo *root, List *final_tlist);
 extern void add_vars_to_targetlist(PlannerInfo *root, List *vars,
 					   Relids where_needed, bool create_new_ph);
#46Ronan Dunklau
rdunklau@gmail.com
In reply to: Albe Laurenz (#45)
Re: [v9.3] writable foreign tables

Hello.

I've tried to implement this API for our Multicorn FDW, and I have a few
questions about the API.

First, I don't understand what are the requirements on the "rowid"
pseudo-column values.

In particular, this sentence from a previous mail makes it ambiguous to me:

At the beginning, I constructed the rowid pseudo-column using
INTERNALOID, but it made troubles when fetched tuples are
materialized prior to table modification.
So, the "rowid" argument of them are re-defined as "const char *".

Does that mean that one should only store a cstring in the rowid
pseudo-column ? Or can one store an arbitrary pointer ? Currently, I'm
building a text value using cstring_to_text_with_len. Could there be a
problem with that ?

Secondly, how does one use a returning clause ?
I've tried to look at the postgres_fdw code, but it does not seem to handle
that.

For what its worth, knowing that the postgres_fdw is still in WIP status,
here is a couple result of my experimentation with it:

- Insert into a foreign table mapped to a table with a "before" trigger,
using a returning clause, will return the values as they were before being
modified by the trigger.
- Same thing, but if the trigger prevents insertion (returning NULL), the
"would-have-been" inserted row is still returned, although the insert
statement reports zero rows.
- Inserting into a table with default values does not work as intended,
since missing values are replaced by a null value in the remote statement.

What can be done to make the behaviour more consistent ?

I'm very excited about this feature, thank you for making this possible.

Regards,
--
Ronan Dunklau

2012/12/14 Albe Laurenz <laurenz.albe@wien.gv.at>

Show quoted text

Kohei KaiGai wrote:

I came up with one more query that causes a problem:

[...]

This causes a deadlock, but one that is not detected;
the query just keeps hanging.

The UPDATE in the CTE has the rows locked, so the
SELECT ... FOR UPDATE issued via the FDW connection will hang
indefinitely.

I wonder if that's just a pathological corner case that we shouldn't
worry about. Loopback connections for FDWs themselves might not
be so rare, for example as a substitute for autonomous subtransactions.

I guess it is not easily possible to detect such a situation or
to do something reasonable about it.

It is not avoidable problem due to the nature of distributed database

system,

not only loopback scenario.

In my personal opinion, I'd like to wait for someone implements

distributed

lock/transaction manager on top of the background worker framework being
recently committed, to intermediate lock request.
However, it will take massive amount of efforts to existing

lock/transaction

management layer, not only enhancement of FDW APIs. It is obviously out
of scope in this patch.

So, I'd like to suggest authors of FDW that support writable features to

put

mention about possible deadlock scenario in their documentation.
At least, above writable CTE example is a situation that two different

sessions

concurrently update the "test" relation, thus, one shall be blocked.

Fair enough.

I tried to overhaul the documentation, see the attached patch.

There was one thing that I was not certain of:
You say that for writable foreign tables, BeginForeignModify
and EndForeignModify *must* be implemented.
I thought that these were optional, and if you can do your work

with just, say, ExecForeignDelete, you could do that.

Yes, that's right. What I wrote was incorrect.
If FDW driver does not require any state during modification of
foreign tables, indeed, these are not mandatory handler.

I have updated the documentation, that was all I changed in the
attached patches.

OK. I split the patch into two portion, part-1 is the APIs relevant
patch, part-2 is relevant to postgres_fdw patch.

Great.

I'll mark the patch as "ready for committer".

Yours,
Laurenz Albe

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#47Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Ronan Dunklau (#46)
Re: [v9.3] writable foreign tables

Hi,

2012/12/18 Ronan Dunklau <rdunklau@gmail.com>:

Hello.

I've tried to implement this API for our Multicorn FDW, and I have a few
questions about the API.

First, I don't understand what are the requirements on the "rowid"
pseudo-column values.

In particular, this sentence from a previous mail makes it ambiguous to me:

At the beginning, I constructed the rowid pseudo-column using
INTERNALOID, but it made troubles when fetched tuples are
materialized prior to table modification.
So, the "rowid" argument of them are re-defined as "const char *".

Does that mean that one should only store a cstring in the rowid
pseudo-column ? Or can one store an arbitrary pointer ? Currently, I'm
building a text value using cstring_to_text_with_len. Could there be a
problem with that ?

All what we require on the rowid pseudo-column is it has capability to
identify a particular row on the remote side. In case of postgres_fdw,
ctid of the relevant table is sufficient for the purpose.
I don't recommend to set the rowid field an arbitrary pointer, because
scanned tuple may be materialized between scanning and modifying
foreign table, thus, the arbitrary pointer shall be dealt under the
assumption of cstring data type.

Secondly, how does one use a returning clause ?
I've tried to look at the postgres_fdw code, but it does not seem to handle
that.

For what its worth, knowing that the postgres_fdw is still in WIP status,
here is a couple result of my experimentation with it:

- Insert into a foreign table mapped to a table with a "before" trigger,
using a returning clause, will return the values as they were before being
modified by the trigger.
- Same thing, but if the trigger prevents insertion (returning NULL), the
"would-have-been" inserted row is still returned, although the insert
statement reports zero rows.

Hmm. Do you want to see the "real" final contents of the rows being inserted,
don't you. Yep, the proposed interface does not have capability to modify
the supplied tuple on ExecForeignInsert or other methods.

Probably, it needs to adjust interface to allow FDW drivers to return
a modified HeapTuple or TupleTableSlot for RETURNING calculation.
(As trigger doing, it can return the given one as-is if no change)

Let me investigate the code around this topic.

- Inserting into a table with default values does not work as intended,
since missing values are replaced by a null value in the remote statement.

It might be a bug of my proof-of-concept portion at postgres_fdw.
The prepared INSERT statement should list up columns being actually
used only, not all ones unconditionally.

Thanks,

What can be done to make the behaviour more consistent ?

I'm very excited about this feature, thank you for making this possible.

Regards,
--
Ronan Dunklau

2012/12/14 Albe Laurenz <laurenz.albe@wien.gv.at>

Kohei KaiGai wrote:

I came up with one more query that causes a problem:

[...]

This causes a deadlock, but one that is not detected;
the query just keeps hanging.

The UPDATE in the CTE has the rows locked, so the
SELECT ... FOR UPDATE issued via the FDW connection will hang
indefinitely.

I wonder if that's just a pathological corner case that we shouldn't
worry about. Loopback connections for FDWs themselves might not
be so rare, for example as a substitute for autonomous subtransactions.

I guess it is not easily possible to detect such a situation or
to do something reasonable about it.

It is not avoidable problem due to the nature of distributed database
system,
not only loopback scenario.

In my personal opinion, I'd like to wait for someone implements
distributed
lock/transaction manager on top of the background worker framework being
recently committed, to intermediate lock request.
However, it will take massive amount of efforts to existing
lock/transaction
management layer, not only enhancement of FDW APIs. It is obviously out
of scope in this patch.

So, I'd like to suggest authors of FDW that support writable features to
put
mention about possible deadlock scenario in their documentation.
At least, above writable CTE example is a situation that two different
sessions
concurrently update the "test" relation, thus, one shall be blocked.

Fair enough.

I tried to overhaul the documentation, see the attached patch.

There was one thing that I was not certain of:
You say that for writable foreign tables, BeginForeignModify
and EndForeignModify *must* be implemented.
I thought that these were optional, and if you can do your work

with just, say, ExecForeignDelete, you could do that.

Yes, that's right. What I wrote was incorrect.
If FDW driver does not require any state during modification of
foreign tables, indeed, these are not mandatory handler.

I have updated the documentation, that was all I changed in the
attached patches.

OK. I split the patch into two portion, part-1 is the APIs relevant
patch, part-2 is relevant to postgres_fdw patch.

Great.

I'll mark the patch as "ready for committer".

Yours,
Laurenz Albe

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

--
KaiGai Kohei <kaigai@kaigai.gr.jp>

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#48Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Kohei KaiGai (#47)
2 attachment(s)
Re: [v9.3] writable foreign tables

The attached patches are revised version for before-row triggers or
default setting.

I adjusted APIs to allow FDW drivers to modify the supplied slot
according to the row
being actually inserted / updated on the remote side.
Now, relevant handlers are declared as follows:

TupleTableSlot *
ExecForeignInsert (ResultRelInfo *resultRelInfo,
TupleTableSlot *slot);

TupleTableSlot *
ExecForeignUpdate (ResultRelInfo *resultRelInfo,
const char *rowid,
TupleTableSlot *slot);

bool
ExecForeignDelete (ResultRelInfo *resultRelInfo,
const char *rowid);

If nothing were modified, insert or update handler usually returns the supplied
slot as is. Elsewhere, it can return a modified image of tuple according to the
result of remote query execution, including default setting or
before-row trigger.

Let's see the following examples with enhanced postgres_fdw.

postgres=# CREATE FOREIGN TABLE ft1 (a int, b text, c date) SERVER
loopback OPTIONS(relname 't1');
CREATE FOREIGN TABLE
postgres=# SELECT * FROM ft1;
a | b | c
---+-----+------------
1 | aaa | 2012-12-10
2 | bbb | 2012-12-15
3 | ccc | 2012-12-20
(3 rows)

==> ft1 is a foreign table behalf of t1 on loopback server.

postgres=# ALTER TABLE t1 ALTER c SET DEFAULT current_date;
ALTER TABLE
postgres=# CREATE OR REPLACE FUNCTION t1_trig_func() RETURNS trigger AS $$
postgres$# BEGIN
postgres$# NEW.b = NEW.b || '_trig_update';
postgres$# RETURN NEW;
postgres$# END;
postgres$# $$ LANGUAGE plpgsql;
CREATE FUNCTION
postgres=# CREATE TRIGGER t1_br_trig BEFORE INSERT OR UPDATE ON t1 FOR
EACH ROW EXECUTE PROCEDURE t1_trig_func();
CREATE TRIGGER

==> The "t1" table has default setting and before-row triggers.

postgres=# INSERT INTO ft1 VALUES (4,'ddd','2012-12-05'),
(5,'eee',null) RETURNING *;
a | b | c
---+-----------------+------------
4 | ddd_trig_update | 2012-12-05
5 | eee_trig_update |
(2 rows)

INSERT 0 2
postgres=# INSERT INTO ft1 VALUES (6, 'fff') RETURNING *;
a | b | c
---+-----------------+------------
6 | fff_trig_update | 2012-12-23
(1 row)

INSERT 0 1

==> RETURNING clause can back modified image on the remote side.
Please note that the results are affected by default-setting and
before-row procedure on remote side.

postgres=# UPDATE ft1 SET a = a + 100 RETURNING *;
a | b | c
-----+-----------------------------+------------
101 | aaa_trig_update | 2012-12-10
102 | bbb_trig_update | 2012-12-15
103 | ccc_trig_update | 2012-12-20
104 | ddd_trig_update_trig_update | 2012-12-05
105 | eee_trig_update_trig_update |
106 | fff_trig_update_trig_update | 2012-12-23
(6 rows)

UPDATE 6

==> update command also

The modified API lost a feature to return the number of rows being affected
that might be useful in case when underlying query multiple rows on the
remote side. However, I rethought it does not really make sense.
If FDW driver support a feature to push down whole UPDATE or DELETE
command towards simple queries, it is not so difficult to cache the result
of the query that affect multiple rows, then the relevant routine can pop
the cached result for each invocation without remote query execution on
demand.

Thanks,

2012/12/18 Kohei KaiGai <kaigai@kaigai.gr.jp>:

Hi,

2012/12/18 Ronan Dunklau <rdunklau@gmail.com>:

Hello.

I've tried to implement this API for our Multicorn FDW, and I have a few
questions about the API.

First, I don't understand what are the requirements on the "rowid"
pseudo-column values.

In particular, this sentence from a previous mail makes it ambiguous to me:

At the beginning, I constructed the rowid pseudo-column using
INTERNALOID, but it made troubles when fetched tuples are
materialized prior to table modification.
So, the "rowid" argument of them are re-defined as "const char *".

Does that mean that one should only store a cstring in the rowid
pseudo-column ? Or can one store an arbitrary pointer ? Currently, I'm
building a text value using cstring_to_text_with_len. Could there be a
problem with that ?

All what we require on the rowid pseudo-column is it has capability to
identify a particular row on the remote side. In case of postgres_fdw,
ctid of the relevant table is sufficient for the purpose.
I don't recommend to set the rowid field an arbitrary pointer, because
scanned tuple may be materialized between scanning and modifying
foreign table, thus, the arbitrary pointer shall be dealt under the
assumption of cstring data type.

Secondly, how does one use a returning clause ?
I've tried to look at the postgres_fdw code, but it does not seem to handle
that.

For what its worth, knowing that the postgres_fdw is still in WIP status,
here is a couple result of my experimentation with it:

- Insert into a foreign table mapped to a table with a "before" trigger,
using a returning clause, will return the values as they were before being
modified by the trigger.
- Same thing, but if the trigger prevents insertion (returning NULL), the
"would-have-been" inserted row is still returned, although the insert
statement reports zero rows.

Hmm. Do you want to see the "real" final contents of the rows being inserted,
don't you. Yep, the proposed interface does not have capability to modify
the supplied tuple on ExecForeignInsert or other methods.

Probably, it needs to adjust interface to allow FDW drivers to return
a modified HeapTuple or TupleTableSlot for RETURNING calculation.
(As trigger doing, it can return the given one as-is if no change)

Let me investigate the code around this topic.

- Inserting into a table with default values does not work as intended,
since missing values are replaced by a null value in the remote statement.

It might be a bug of my proof-of-concept portion at postgres_fdw.
The prepared INSERT statement should list up columns being actually
used only, not all ones unconditionally.

Thanks,

What can be done to make the behaviour more consistent ?

I'm very excited about this feature, thank you for making this possible.

Regards,
--
Ronan Dunklau

2012/12/14 Albe Laurenz <laurenz.albe@wien.gv.at>

Kohei KaiGai wrote:

I came up with one more query that causes a problem:

[...]

This causes a deadlock, but one that is not detected;
the query just keeps hanging.

The UPDATE in the CTE has the rows locked, so the
SELECT ... FOR UPDATE issued via the FDW connection will hang
indefinitely.

I wonder if that's just a pathological corner case that we shouldn't
worry about. Loopback connections for FDWs themselves might not
be so rare, for example as a substitute for autonomous subtransactions.

I guess it is not easily possible to detect such a situation or
to do something reasonable about it.

It is not avoidable problem due to the nature of distributed database
system,
not only loopback scenario.

In my personal opinion, I'd like to wait for someone implements
distributed
lock/transaction manager on top of the background worker framework being
recently committed, to intermediate lock request.
However, it will take massive amount of efforts to existing
lock/transaction
management layer, not only enhancement of FDW APIs. It is obviously out
of scope in this patch.

So, I'd like to suggest authors of FDW that support writable features to
put
mention about possible deadlock scenario in their documentation.
At least, above writable CTE example is a situation that two different
sessions
concurrently update the "test" relation, thus, one shall be blocked.

Fair enough.

I tried to overhaul the documentation, see the attached patch.

There was one thing that I was not certain of:
You say that for writable foreign tables, BeginForeignModify
and EndForeignModify *must* be implemented.
I thought that these were optional, and if you can do your work

with just, say, ExecForeignDelete, you could do that.

Yes, that's right. What I wrote was incorrect.
If FDW driver does not require any state during modification of
foreign tables, indeed, these are not mandatory handler.

I have updated the documentation, that was all I changed in the
attached patches.

OK. I split the patch into two portion, part-1 is the APIs relevant
patch, part-2 is relevant to postgres_fdw patch.

Great.

I'll mark the patch as "ready for committer".

Yours,
Laurenz Albe

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

--
KaiGai Kohei <kaigai@kaigai.gr.jp>

--
KaiGai Kohei <kaigai@kaigai.gr.jp>

Attachments:

pgsql-v9.3-writable-fdw-poc.v10.part-1.patchapplication/octet-stream; name=pgsql-v9.3-writable-fdw-poc.v10.part-1.patchDownload
 doc/src/sgml/ddl.sgml                   |    5 -
 doc/src/sgml/fdwhandler.sgml            |  170 ++++++++++++++++++++++++++++++-
 src/backend/executor/execMain.c         |   34 ++++++-
 src/backend/executor/nodeForeignscan.c  |  136 ++++++++++++++++++++++++-
 src/backend/executor/nodeModifyTable.c  |  164 ++++++++++++++++++++++++-----
 src/backend/foreign/foreign.c           |   68 +++++++++++++
 src/backend/nodes/copyfuncs.c           |    2 +
 src/backend/nodes/outfuncs.c            |    2 +
 src/backend/optimizer/plan/createplan.c |   48 ++++++++-
 src/backend/optimizer/plan/initsplan.c  |   10 +-
 src/backend/optimizer/plan/planmain.c   |    5 +-
 src/backend/optimizer/plan/planner.c    |    8 +-
 src/backend/optimizer/prep/prepunion.c  |    3 +-
 src/backend/optimizer/util/plancat.c    |   27 ++++-
 src/backend/optimizer/util/relnode.c    |    7 +-
 src/backend/parser/parse_relation.c     |   15 ++-
 src/backend/rewrite/rewriteHandler.c    |   55 ++++++++--
 src/include/foreign/fdwapi.h            |   31 ++++++
 src/include/foreign/foreign.h           |    6 ++
 src/include/nodes/execnodes.h           |   13 ++-
 src/include/nodes/plannodes.h           |    2 +
 src/include/optimizer/pathnode.h        |    2 +-
 src/include/optimizer/plancat.h         |    2 +-
 src/include/optimizer/planmain.h        |    6 +-
 24 files changed, 753 insertions(+), 68 deletions(-)

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 207de9b..8f44d8d 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -3066,11 +3066,6 @@ ANALYZE measurement;
     <firstterm>user mapping</>, which can provide additional options 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.
-   </para>
  </sect1>
 
  <sect1 id="ddl-others">
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index 2d604ed..74f0358 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -89,6 +89,54 @@
 
     <para>
 <programlisting>
+AttrNumber
+GetForeignRelWidth(PlannerInfo *root,
+                   RelOptInfo *baserel,
+                   Relation foreignrel,
+                   bool inhparent,
+                   List *targetList);
+</programlisting>
+     Obtain the width of the result set to be fetched during a foreign table scan.
+     This is an optional handler, and called before <literal>GetForeignRelSize</>
+     for a query involving a foreign table
+     (during the construction of <literal>RelOptInfo</>).
+     <literal>root</> is the planner's global information about the query,
+     <literal>baserel</> is the planner's information being constructed for
+     this query, and <literal>foreignrel</> is a <literal>Relation</>
+     descriptor of the foreign table.
+     <literal>inhparent</> is a boolean to show whether the relation is
+     an inheritance parent, even though foreign tables do not support table
+     inheritance right now. <literal>targetList</> is the list of
+     <literal>TargetEntry</> to be returned from the (sub-)query
+     that is currently in focus.
+    </para>
+
+    <para>
+     The result value of this function will be assigned to
+     <literal>baserel-&gt;max_attr</>, that means it is the expected number
+     of columns being fetched during the foreign table scan.
+     It should not be smaller than the number of regular columns in the definition
+     of this foreign table.  You can only return a number greater than his value to
+     acquire slots for some additional attributes, which are called
+     <firstterm>pseudo-columns</>.
+     A typical usage of a pseudo-column is to carry an identifier of
+     a particular remote row to be updated or deleted from the scanning stage
+     to the modifying stage when the foreign table is the target of
+     a data-modifying SQL statement.
+     You can return the result of the helper function
+     <literal>get_pseudo_rowid_column</> if this <literal>"rowid"</>
+     pseudo-column is the only one you need.
+   </para>
+
+   <para>
+     In addition to that, pseudo-columns can be used to off-load the burden of
+     complex calculations to foreign computing resources by replacing an
+     expression with a reference to its result, which is calculated on the
+     remote side rather than locally.
+   </para>
+
+    <para>
+<programlisting>
 void
 GetForeignRelSize (PlannerInfo *root,
                    RelOptInfo *baserel,
@@ -96,7 +144,8 @@ 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.
+     for a query involving a foreign table at the beginning of planning
+     or right after <literal>GetForeignRelWidth</>, if that callback is configured.
      <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
@@ -315,6 +364,125 @@ AcquireSampleRowsFunc (Relation relation, int elevel,
     </para>
 
     <para>
+     If a FDW supports writable foreign tables, it should implement
+     some or all of the following callback functions depending on
+     the needs and capabilities of the FDW.
+    </para>
+
+    <para>
+<programlisting>
+List *
+PlanForeignModify(PlannerInfo *root,
+                  ModifyTable *plan,
+                  Index resultRelation,
+                  Plan *subplan);
+</programlisting>
+     It allows FDW drivers to construct private information relevant to
+     the modification of the foreign table.  This private information must have
+     the form of a <literal>List *</>, which will be delivered as
+     third argument to <literal>BeginForeignModify</> during the execution stage.
+    </para>
+
+    <para>
+     <literal>root</> is the planner's global information about the query.
+     <literal>plan</> is the master plan to modify the result relation according
+     to its command type.  Please consider that <literal>ModifyTable</>
+     may have multiple result relations in a future revision, even though
+     currently there is no support for table inheritance on foreign tables.
+     <literal>resultRelation</> is an index for the result relation in the
+     range table entries, and <literal>subplan</> is the relevant scan plan;
+     that should be a <literal>ForeignScan</> for <literal>UPDATE</> or
+     <literal>DELETE</>, so the driver can access the private information of
+     the scan stage using this argument.
+    </para>
+
+    <para>
+<programlisting>
+void
+BeginForeignModify (ModifyTableState *mtstate,
+                    ResultRelInfo *resultRelInfo,
+                    List *fdw_private,
+                    Plan *subplan,
+                    int eflags);
+</programlisting>
+     It is invoked at beginning of foreign table modification, during
+     executor startup.  This routine should perform any initialization
+     needed prior to the actual table modifications, but not start
+     modifying the actual tuples. (That should be done during each call of
+     <function>ExecForeignInsert</>, <function>ExecForeignUpdate</> or
+     <function>ExecForeignDelete</>.)
+    </para>
+
+    <para>
+     The <structfield>ri_fdw_state</> field of <structname>ResultRelInfo</>
+     is reserved to store any private information relevant to this foreign table,
+     so it should be initialized to contain the per-scan state.
+     Please consider that <literal>ModifyTableState</> may have multiple
+     result relations in a future revision, even though currently there is no
+     support for table inheritance on foreign tables.  <literal>resultRelInfo</>
+     is the master information connected to this foreign table.
+     <literal>fdw_private</> is private information constructed in
+     <literal>PlanForeignModify</>, and <literal>subplan</> is the relevant
+     scan plan of this table modification.
+    </para>
+
+    <para>
+<programlisting>
+TupleTableSlot *
+ExecForeignInsert (ResultRelInfo *resultRelInfo,
+                   TupleTableSlot *slot);
+</programlisting>
+     Insert the given tuple into backing storage on behalf of the foreign table.
+     The supplied slot shall hold a tuple being already formed according to
+     the definition of the relation, thus all the pseudo-columns are already
+     filtered out.
+     FDW driver can modify the tuple to be inserted (due to before-row-insert
+     triggers at remote side, for example). In this case, this routine can
+     return a modified slot that affects result of <literal>RETURNING</>,
+     or <literal>NULL</> that means this insertion was skipped. Elsewhere,
+     it usually returns the given slot, as is.
+    </para>
+
+    <para>
+<programlisting>
+bool
+ExecForeignDelete (ResultRelInfo *resultRelInfo,
+                   const char *rowid);
+</programlisting>
+     Delete the tuple being identified with <literal>rowid</> from the backing
+     storage on behalf of the foreign table.
+     FDW driver can return a boolean that means whether this deletion was done
+     actually, or not.
+    </para>
+
+    <para>
+<programlisting>
+TupleTableSlot *
+ExecForeignUpdate (ResultRelInfo *resultRelInfo,
+                   const char *rowid,
+                   TupleTableSlot *slot);
+</programlisting>
+     Update the tuple being identified with <literal>rowid</> in the backing
+     storage on behalf of the foreign table with the given slot that shall
+     hold a newer version of tuple.
+     FDW driver can modify the tuple to be updated (due to before-row-update
+     triggers at remote side, for example). In this case, this routine can
+     return a modified slot that affects result of <literal>RETURNING</>,
+     or <literal>NULL</> that means this update was skipped. Elsewhere,
+     it usually returns the given slot, as is.
+    </para>
+
+    <para>
+<programlisting>
+void
+EndForeignModify (ResultRelInfo *resultRelInfo);
+</programlisting>
+     End the modification 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>
      The <structname>FdwRoutine</> struct type is declared in
      <filename>src/include/foreign/fdwapi.h</>, which see for additional
      details.
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 0222d40..60f4c94 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"
@@ -933,6 +934,7 @@ void
 CheckValidResultRel(Relation resultRel, CmdType operation)
 {
 	TriggerDesc *trigDesc = resultRel->trigdesc;
+	FdwRoutine	*fdwroutine;
 
 	switch (resultRel->rd_rel->relkind)
 	{
@@ -991,10 +993,34 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
 			}
 			break;
 		case RELKIND_FOREIGN_TABLE:
-			ereport(ERROR,
-					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("cannot change foreign table \"%s\"",
-							RelationGetRelationName(resultRel))));
+			fdwroutine = GetFdwRoutineByRelId(RelationGetRelid(resultRel));
+			switch (operation)
+			{
+				case CMD_INSERT:
+					if (!fdwroutine || !fdwroutine->ExecForeignInsert)
+						ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("cannot insert into foreign table \"%s\"",
+									RelationGetRelationName(resultRel))));
+					break;
+				case CMD_UPDATE:
+					if (!fdwroutine || !fdwroutine->ExecForeignUpdate)
+						ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("cannot update foreign table \"%s\"",
+									RelationGetRelationName(resultRel))));
+					break;
+				case CMD_DELETE:
+					if (!fdwroutine || !fdwroutine->ExecForeignDelete)
+						ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("cannot delete from foreign table \"%s\"",
+									RelationGetRelationName(resultRel))));
+					break;
+				default:
+					elog(ERROR, "unrecognized CmdType: %d", (int) operation);
+					break;
+			}
 			break;
 		default:
 			ereport(ERROR,
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 9204859..1ee626f 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -25,6 +25,7 @@
 #include "executor/executor.h"
 #include "executor/nodeForeignscan.h"
 #include "foreign/fdwapi.h"
+#include "nodes/nodeFuncs.h"
 #include "utils/rel.h"
 
 static TupleTableSlot *ForeignNext(ForeignScanState *node);
@@ -93,6 +94,133 @@ ExecForeignScan(ForeignScanState *node)
 					(ExecScanRecheckMtd) ForeignRecheck);
 }
 
+/*
+ * pseudo_column_walker
+ *
+ * helper routine of GetPseudoTupleDesc. It pulls Var nodes that reference
+ * pseudo columns from targetlis of the relation
+ */
+typedef struct
+{
+	Relation	relation;
+	Index		varno;
+	List	   *pcolumns;
+	AttrNumber	max_attno;
+} pseudo_column_walker_context;
+
+static bool
+pseudo_column_walker(Node *node, pseudo_column_walker_context *context)
+{
+	if (node == NULL)
+		return false;
+	if (IsA(node, Var))
+	{
+		Var		   *var = (Var *) node;
+		ListCell   *cell;
+
+		if (var->varno == context->varno && var->varlevelsup == 0 &&
+			var->varattno > RelationGetNumberOfAttributes(context->relation))
+		{
+			foreach (cell, context->pcolumns)
+			{
+				Var	   *temp = lfirst(cell);
+
+				if (temp->varattno == var->varattno)
+				{
+					if (!equal(var, temp))
+						elog(ERROR, "asymmetric pseudo column appeared");
+					break;
+				}
+			}
+			if (!cell)
+			{
+				context->pcolumns = lappend(context->pcolumns, var);
+				if (var->varattno > context->max_attno)
+					context->max_attno = var->varattno;
+			}
+		}
+		return false;
+	}
+
+	/* Should not find an unplanned subquery */
+	Assert(!IsA(node, Query));
+
+	return expression_tree_walker(node, pseudo_column_walker,
+								  (void *)context);
+}
+
+/*
+ * GetPseudoTupleDesc
+ *
+ * It generates TupleDesc structure including pseudo-columns if required.
+ */
+static TupleDesc
+GetPseudoTupleDesc(ForeignScan *node, Relation relation)
+{
+	pseudo_column_walker_context context;
+	List	   *target_list = node->scan.plan.targetlist;
+	TupleDesc	tupdesc;
+	AttrNumber	attno;
+	ListCell   *cell;
+	ListCell   *prev;
+	bool		hasoid;
+
+	context.relation = relation;
+	context.varno = node->scan.scanrelid;
+	context.pcolumns = NIL;
+	context.max_attno = -1;
+
+	pseudo_column_walker((Node *)target_list, (void *)&context);
+	Assert(context.max_attno > RelationGetNumberOfAttributes(relation));
+
+	hasoid = RelationGetForm(relation)->relhasoids;
+	tupdesc = CreateTemplateTupleDesc(context.max_attno, hasoid);
+
+	for (attno = 1; attno <= context.max_attno; attno++)
+	{
+		/* case of regular columns */
+		if (attno <= RelationGetNumberOfAttributes(relation))
+		{
+			memcpy(tupdesc->attrs[attno - 1],
+				   RelationGetDescr(relation)->attrs[attno - 1],
+				   ATTRIBUTE_FIXED_PART_SIZE);
+			continue;
+		}
+
+		/* case of pseudo columns */
+		prev = NULL;
+		foreach (cell, context.pcolumns)
+		{
+			Var	   *var = lfirst(cell);
+
+			if (var->varattno == attno)
+			{
+				char		namebuf[NAMEDATALEN];
+
+				snprintf(namebuf, sizeof(namebuf),
+						 "pseudo_column_%d", attno);
+
+				TupleDescInitEntry(tupdesc,
+								   attno,
+								   namebuf,
+								   var->vartype,
+								   var->vartypmod,
+								   0);
+				TupleDescInitEntryCollation(tupdesc,
+											attno,
+											var->varcollid);
+				context.pcolumns
+					= list_delete_cell(context.pcolumns, cell, prev);
+				break;
+			}
+			prev = cell;
+		}
+		if (!cell)
+			elog(ERROR, "pseudo column %d of %s not in target list",
+				 attno, RelationGetRelationName(relation));
+	}
+	return tupdesc;
+}
 
 /* ----------------------------------------------------------------
  *		ExecInitForeignScan
@@ -103,6 +231,7 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
 {
 	ForeignScanState *scanstate;
 	Relation	currentRelation;
+	TupleDesc	tupdesc;
 	FdwRoutine *fdwroutine;
 
 	/* check for unsupported flags */
@@ -149,7 +278,12 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
 	/*
 	 * get the scan type from the relation descriptor.
 	 */
-	ExecAssignScanType(&scanstate->ss, RelationGetDescr(currentRelation));
+	if (node->fsPseudoCol)
+		tupdesc = GetPseudoTupleDesc(node, currentRelation);
+	else
+		tupdesc = RelationGetDescr(currentRelation);
+
+	ExecAssignScanType(&scanstate->ss, tupdesc);
 
 	/*
 	 * Initialize result tuple type and projection info.
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d31015c..6b2e72e 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,20 @@ ExecInsert(TupleTableSlot *slot,
 
 		newId = InvalidOid;
 	}
+	else if (resultRelInfo->ri_fdwroutine)
+	{
+		FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+		slot = fdwroutine->ExecForeignInsert(resultRelInfo, slot);
+
+		if (slot == NULL)		/* "do nothing" */
+			return NULL;
+
+		/* FDW driver might have changed tuple */
+		tuple = ExecMaterializeSlot(slot);
+
+		newId = InvalidOid;
+	}
 	else
 	{
 		/*
@@ -252,7 +267,7 @@ ExecInsert(TupleTableSlot *slot,
 
 	if (canSetTag)
 	{
-		(estate->es_processed)++;
+		(estate->es_processed) ++;
 		estate->es_lastoid = newId;
 		setLastTid(&(tuple->t_self));
 	}
@@ -285,7 +300,7 @@ ExecInsert(TupleTableSlot *slot,
  * ----------------------------------------------------------------
  */
 static TupleTableSlot *
-ExecDelete(ItemPointer tupleid,
+ExecDelete(Datum rowid,
 		   HeapTupleHeader oldtuple,
 		   TupleTableSlot *planSlot,
 		   EPQState *epqstate,
@@ -310,7 +325,7 @@ ExecDelete(ItemPointer tupleid,
 		bool		dodelete;
 
 		dodelete = ExecBRDeleteTriggers(estate, epqstate, resultRelInfo,
-										tupleid);
+										(ItemPointer)DatumGetPointer(rowid));
 
 		if (!dodelete)			/* "do nothing" */
 			return NULL;
@@ -334,8 +349,20 @@ ExecDelete(ItemPointer tupleid,
 		if (!dodelete)			/* "do nothing" */
 			return NULL;
 	}
+	else if (resultRelInfo->ri_fdwroutine)
+	{
+		FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+		bool		dodelete;
+
+		dodelete = fdwroutine->ExecForeignDelete(resultRelInfo,
+												 DatumGetCString(rowid));
+		if (!dodelete)
+			return NULL;		/* "do nothing" */
+	}
 	else
 	{
+		ItemPointer	tupleid = (ItemPointer) DatumGetPointer(rowid);
+
 		/*
 		 * delete the tuple
 		 *
@@ -430,10 +457,11 @@ ldelete:;
 	}
 
 	if (canSetTag)
-		(estate->es_processed)++;
+		(estate->es_processed) ++;
 
 	/* AFTER ROW DELETE Triggers */
-	ExecARDeleteTriggers(estate, resultRelInfo, tupleid);
+	ExecARDeleteTriggers(estate, resultRelInfo,
+						 (ItemPointer)DatumGetPointer(rowid));
 
 	/* Process RETURNING if present */
 	if (resultRelInfo->ri_projectReturning)
@@ -457,7 +485,8 @@ ldelete:;
 		}
 		else
 		{
-			deltuple.t_self = *tupleid;
+			ItemPointerCopy((ItemPointer)DatumGetPointer(rowid),
+							&deltuple.t_self);
 			if (!heap_fetch(resultRelationDesc, SnapshotAny,
 							&deltuple, &delbuffer, false, NULL))
 				elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
@@ -499,7 +528,7 @@ ldelete:;
  * ----------------------------------------------------------------
  */
 static TupleTableSlot *
-ExecUpdate(ItemPointer tupleid,
+ExecUpdate(Datum rowid,
 		   HeapTupleHeader oldtuple,
 		   TupleTableSlot *slot,
 		   TupleTableSlot *planSlot,
@@ -537,7 +566,7 @@ ExecUpdate(ItemPointer tupleid,
 		resultRelInfo->ri_TrigDesc->trig_update_before_row)
 	{
 		slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
-									tupleid, slot);
+									(ItemPointer)DatumGetPointer(rowid), slot);
 
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
@@ -567,8 +596,23 @@ ExecUpdate(ItemPointer tupleid,
 		/* trigger might have changed tuple */
 		tuple = ExecMaterializeSlot(slot);
 	}
+	else if (resultRelInfo->ri_fdwroutine)
+	{
+		FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+		slot = fdwroutine->ExecForeignUpdate(resultRelInfo,
+											 DatumGetCString(rowid),
+											 slot);
+		if (slot == NULL)		/* "do nothing" */
+			return NULL;
+
+		/* FDW driver might have changed tuple */
+		tuple = ExecMaterializeSlot(slot);
+	}
 	else
 	{
+		ItemPointer	tupleid = (ItemPointer) DatumGetPointer(rowid);
+
 		/*
 		 * Check the constraints of the tuple
 		 *
@@ -687,11 +731,12 @@ lreplace:;
 	}
 
 	if (canSetTag)
-		(estate->es_processed)++;
+		(estate->es_processed) ++;
 
 	/* AFTER ROW UPDATE Triggers */
-	ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple,
-						 recheckIndexes);
+	ExecARUpdateTriggers(estate, resultRelInfo,
+						 (ItemPointer) DatumGetPointer(rowid),
+						 tuple, recheckIndexes);
 
 	list_free(recheckIndexes);
 
@@ -771,6 +816,7 @@ ExecModifyTable(ModifyTableState *node)
 	TupleTableSlot *planSlot;
 	ItemPointer tupleid = NULL;
 	ItemPointerData tuple_ctid;
+	Datum		rowid = 0;
 	HeapTupleHeader oldtuple = NULL;
 
 	/*
@@ -859,17 +905,19 @@ ExecModifyTable(ModifyTableState *node)
 		if (junkfilter != NULL)
 		{
 			/*
-			 * extract the 'ctid' or 'wholerow' junk attribute.
+			 * extract the 'ctid', 'rowid' or 'wholerow' junk attribute.
 			 */
 			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,
+												 junkfilter->jf_junkRowidNo,
 												 &isNull);
 					/* shouldn't ever get a null result... */
 					if (isNull)
@@ -877,13 +925,33 @@ ExecModifyTable(ModifyTableState *node)
 
 					tupleid = (ItemPointer) DatumGetPointer(datum);
 					tuple_ctid = *tupleid;		/* be sure we don't free
-												 * ctid!! */
-					tupleid = &tuple_ctid;
+												 * ctid ! */
+					rowid = PointerGetDatum(&tuple_ctid);
+				}
+				else if (relkind == RELKIND_FOREIGN_TABLE)
+				{
+					datum = ExecGetJunkAttribute(slot,
+												 junkfilter->jf_junkRowidNo,
+												 &isNull);
+					/* shouldn't ever get a null result... */
+					if (isNull)
+						elog(ERROR, "rowid is NULL");
+
+					rowid = datum;
+
+					datum = ExecGetJunkAttribute(slot,
+												 junkfilter->jf_junkRecordNo,
+												 &isNull);
+					/* shouldn't ever get a null result... */
+					if (isNull)
+						elog(ERROR, "wholerow is NULL");
+
+					oldtuple = DatumGetHeapTupleHeader(datum);
 				}
 				else
 				{
 					datum = ExecGetJunkAttribute(slot,
-												 junkfilter->jf_junkAttNo,
+												 junkfilter->jf_junkRecordNo,
 												 &isNull);
 					/* shouldn't ever get a null result... */
 					if (isNull)
@@ -906,11 +974,11 @@ ExecModifyTable(ModifyTableState *node)
 				slot = ExecInsert(slot, planSlot, estate, node->canSetTag);
 				break;
 			case CMD_UPDATE:
-				slot = ExecUpdate(tupleid, oldtuple, slot, planSlot,
+				slot = ExecUpdate(rowid, oldtuple, slot, planSlot,
 								&node->mt_epqstate, estate, node->canSetTag);
 				break;
 			case CMD_DELETE:
-				slot = ExecDelete(tupleid, oldtuple, planSlot,
+				slot = ExecDelete(rowid, oldtuple, planSlot,
 								&node->mt_epqstate, estate, node->canSetTag);
 				break;
 			default:
@@ -997,6 +1065,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	i = 0;
 	foreach(l, node->plans)
 	{
+		char	relkind;
+
 		subplan = (Plan *) lfirst(l);
 
 		/*
@@ -1022,6 +1092,24 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		estate->es_result_relation_info = resultRelInfo;
 		mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
 
+		/*
+		 * Also tells FDW extensions to init the plan for this result rel
+		 */
+		relkind = RelationGetForm(resultRelInfo->ri_RelationDesc)->relkind;
+		if (relkind == RELKIND_FOREIGN_TABLE)
+		{
+			Oid		relid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+			FdwRoutine *fdwroutine = GetFdwRoutineByRelId(relid);
+			List   *fdwprivate = list_nth(node->fdwPrivList, i);
+
+			Assert(fdwroutine != NULL);
+			resultRelInfo->ri_fdwroutine = fdwroutine;
+			resultRelInfo->ri_fdw_state = NULL;
+
+			if (fdwroutine->BeginForeignModify)
+				fdwroutine->BeginForeignModify(mtstate, resultRelInfo,
+											   fdwprivate, subplan, eflags);
+		}
 		resultRelInfo++;
 		i++;
 	}
@@ -1163,6 +1251,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			for (i = 0; i < nplans; i++)
 			{
 				JunkFilter *j;
+				char		relkind =
+				    RelationGetForm(resultRelInfo->ri_RelationDesc)->relkind;
 
 				subplan = mtstate->mt_plans[i]->plan;
 				if (operation == CMD_INSERT || operation == CMD_UPDATE)
@@ -1176,16 +1266,27 @@ 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)
+					if (relkind == RELKIND_RELATION)
 					{
-						j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
-						if (!AttributeNumberIsValid(j->jf_junkAttNo))
+						j->jf_junkRowidNo = ExecFindJunkAttribute(j, "ctid");
+						if (!AttributeNumberIsValid(j->jf_junkRowidNo))
 							elog(ERROR, "could not find junk ctid column");
 					}
+					else if (relkind == RELKIND_FOREIGN_TABLE)
+					{
+						j->jf_junkRowidNo = ExecFindJunkAttribute(j, "rowid");
+						if (!AttributeNumberIsValid(j->jf_junkRowidNo))
+							elog(ERROR, "could not find junk rowid column");
+						j->jf_junkRecordNo
+							= ExecFindJunkAttribute(j, "record");
+						if (!AttributeNumberIsValid(j->jf_junkRecordNo))
+							elog(ERROR, "could not find junk wholerow column");
+					}
 					else
 					{
-						j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
-						if (!AttributeNumberIsValid(j->jf_junkAttNo))
+						j->jf_junkRecordNo
+							= ExecFindJunkAttribute(j, "wholerow");
+						if (!AttributeNumberIsValid(j->jf_junkRecordNo))
 							elog(ERROR, "could not find junk wholerow column");
 					}
 				}
@@ -1239,6 +1340,21 @@ ExecEndModifyTable(ModifyTableState *node)
 {
 	int			i;
 
+	/* Let the FDW shut dowm */
+	for (i=0; i < node->ps.state->es_num_result_relations; i++)
+	{
+		ResultRelInfo  *resultRelInfo
+			= &node->ps.state->es_result_relations[i];
+
+		if (resultRelInfo->ri_fdwroutine)
+		{
+			FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+			if (fdwroutine->EndForeignModify)
+				fdwroutine->EndForeignModify(resultRelInfo);
+		}
+	}
+
 	/*
 	 * Free the exprcontext
 	 */
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
index d8845aa..4363a28 100644
--- a/src/backend/foreign/foreign.c
+++ b/src/backend/foreign/foreign.c
@@ -571,3 +571,71 @@ get_foreign_server_oid(const char *servername, bool missing_ok)
 				 errmsg("server \"%s\" does not exist", servername)));
 	return oid;
 }
+
+/*
+ * get_pseudo_rowid_column
+ *
+ * It picks up an attribute number to be used for the pseudo rowid column,
+ * if it exists.  It should be injected at rewriteHandler.c if the supplied query
+ * is a UPDATE or DELETE command.  Elsewhere, it returns InvalidAttrNumber.
+ */
+AttrNumber
+get_pseudo_rowid_column(RelOptInfo *baserel, List *targetList)
+{
+	ListCell   *cell;
+
+	foreach (cell, targetList)
+	{
+		TargetEntry *tle = lfirst(cell);
+
+		if (tle->resjunk &&
+			tle->resname && strcmp(tle->resname, "rowid") == 0)
+		{
+			Var	   *var;
+
+			if (!IsA(tle->expr, Var))
+				elog(ERROR, "unexpected node on junk rowid entry: %d",
+					 (int) nodeTag(tle->expr));
+
+			var = (Var *) tle->expr;
+			if (baserel->relid == var->varno)
+				return var->varattno;
+		}
+	}
+	return InvalidAttrNumber;
+}
+
+/*
+ * lookup_foreign_scan_plan
+ *
+ * It looks up the ForeignScan plan node with the supplied range-table id.
+ * Its typical usage is for PlanForeignModify to get the underlying scan plan on
+ * UPDATE or DELETE commands.
+ */
+ForeignScan *
+lookup_foreign_scan_plan(Plan *subplan, Index rtindex)
+{
+	if (!subplan)
+		return NULL;
+
+	else if (IsA(subplan, ForeignScan))
+	{
+		ForeignScan	   *fscan = (ForeignScan *) subplan;
+
+		if (fscan->scan.scanrelid == rtindex)
+			return fscan;
+	}
+	else
+	{
+		ForeignScan    *fscan;
+
+		fscan = lookup_foreign_scan_plan(subplan->lefttree, rtindex);
+		if (fscan != NULL)
+			return fscan;
+
+		fscan = lookup_foreign_scan_plan(subplan->righttree, rtindex);
+		if (fscan != NULL)
+			return fscan;
+	}
+	return NULL;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 9387ee9..5b1e8ac 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -181,6 +181,7 @@ _copyModifyTable(const ModifyTable *from)
 	COPY_NODE_FIELD(returningLists);
 	COPY_NODE_FIELD(rowMarks);
 	COPY_SCALAR_FIELD(epqParam);
+	COPY_NODE_FIELD(fdwPrivList);
 
 	return newnode;
 }
@@ -594,6 +595,7 @@ _copyForeignScan(const ForeignScan *from)
 	COPY_NODE_FIELD(fdw_exprs);
 	COPY_NODE_FIELD(fdw_private);
 	COPY_SCALAR_FIELD(fsSystemCol);
+	COPY_SCALAR_FIELD(fsPseudoCol);
 
 	return newnode;
 }
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 35c6287..c32ebd3 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -335,6 +335,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
 	WRITE_NODE_FIELD(returningLists);
 	WRITE_NODE_FIELD(rowMarks);
 	WRITE_INT_FIELD(epqParam);
+	WRITE_NODE_FIELD(fdwPrivList);
 }
 
 static void
@@ -562,6 +563,7 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
 	WRITE_NODE_FIELD(fdw_exprs);
 	WRITE_NODE_FIELD(fdw_private);
 	WRITE_BOOL_FIELD(fsSystemCol);
+	WRITE_BOOL_FIELD(fsPseudoCol);
 }
 
 static void
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 030f420..91db1b8 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -39,6 +39,7 @@
 #include "parser/parse_clause.h"
 #include "parser/parsetree.h"
 #include "utils/lsyscache.h"
+#include "utils/rel.h"
 
 
 static Plan *create_plan_recurse(PlannerInfo *root, Path *best_path);
@@ -1943,6 +1944,8 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 	RelOptInfo *rel = best_path->path.parent;
 	Index		scan_relid = rel->relid;
 	RangeTblEntry *rte;
+	Relation	relation;
+	AttrNumber	num_attrs;
 	int			i;
 
 	/* it should be a base rel... */
@@ -2001,6 +2004,22 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 		}
 	}
 
+	/*
+	 * Also, detect whether any pseudo columns are requested from rel.
+	 */
+	relation = heap_open(rte->relid, NoLock);
+	scan_plan->fsPseudoCol = false;
+	num_attrs = RelationGetNumberOfAttributes(relation);
+	for (i = num_attrs + 1; i <= rel->max_attr; i++)
+	{
+		if (!bms_is_empty(rel->attr_needed[i - rel->min_attr]))
+		{
+			scan_plan->fsPseudoCol = true;
+			break;
+		}
+	}
+	heap_close(relation, NoLock);
+
 	return scan_plan;
 }
 
@@ -4695,7 +4714,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)
@@ -4704,6 +4724,8 @@ make_modifytable(CmdType operation, bool canSetTag,
 	Plan	   *plan = &node->plan;
 	double		total_size;
 	ListCell   *subnode;
+	ListCell   *resultRel;
+	List	   *fdw_priv_list = NIL;
 
 	Assert(list_length(resultRelations) == list_length(subplans));
 	Assert(returningLists == NIL ||
@@ -4746,6 +4768,30 @@ make_modifytable(CmdType operation, bool canSetTag,
 	node->rowMarks = rowMarks;
 	node->epqParam = epqParam;
 
+	/*
+	 * Allow FDW driver to construct its private plan if the result relation
+	 * is a foreign table.
+	 */
+	forboth (resultRel, resultRelations, subnode, subplans)
+	{
+		RangeTblEntry  *rte = rt_fetch(lfirst_int(resultRel),
+									   root->parse->rtable);
+		List		   *fdw_private = NIL;
+		char			relkind = get_rel_relkind(rte->relid);
+
+		if (relkind == RELKIND_FOREIGN_TABLE)
+		{
+			FdwRoutine *fdwroutine = GetFdwRoutineByRelId(rte->relid);
+
+			if (fdwroutine && fdwroutine->PlanForeignModify)
+				fdw_private = fdwroutine->PlanForeignModify(root, node,
+													lfirst_int(resultRel),
+													(Plan *) lfirst(subnode));
+		}
+		fdw_priv_list = lappend(fdw_priv_list, fdw_private);
+	}
+	node->fdwPrivList = fdw_priv_list;
+
 	return node;
 }
 
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index bd719b5..57ba192 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -85,7 +85,7 @@ static void check_hashjoinable(RestrictInfo *restrictinfo);
  * "other rel" RelOptInfos for the members of any appendrels we find here.)
  */
 void
-add_base_rels_to_query(PlannerInfo *root, Node *jtnode)
+add_base_rels_to_query(PlannerInfo *root, List *tlist, Node *jtnode)
 {
 	if (jtnode == NULL)
 		return;
@@ -93,7 +93,7 @@ add_base_rels_to_query(PlannerInfo *root, Node *jtnode)
 	{
 		int			varno = ((RangeTblRef *) jtnode)->rtindex;
 
-		(void) build_simple_rel(root, varno, RELOPT_BASEREL);
+		(void) build_simple_rel(root, varno, tlist, RELOPT_BASEREL);
 	}
 	else if (IsA(jtnode, FromExpr))
 	{
@@ -101,14 +101,14 @@ add_base_rels_to_query(PlannerInfo *root, Node *jtnode)
 		ListCell   *l;
 
 		foreach(l, f->fromlist)
-			add_base_rels_to_query(root, lfirst(l));
+			add_base_rels_to_query(root, tlist, lfirst(l));
 	}
 	else if (IsA(jtnode, JoinExpr))
 	{
 		JoinExpr   *j = (JoinExpr *) jtnode;
 
-		add_base_rels_to_query(root, j->larg);
-		add_base_rels_to_query(root, j->rarg);
+		add_base_rels_to_query(root, tlist, j->larg);
+		add_base_rels_to_query(root, tlist, j->rarg);
 	}
 	else
 		elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
index c2488a4..4442444 100644
--- a/src/backend/optimizer/plan/planmain.c
+++ b/src/backend/optimizer/plan/planmain.c
@@ -112,6 +112,9 @@ query_planner(PlannerInfo *root, List *tlist,
 	 */
 	if (parse->jointree->fromlist == NIL)
 	{
+		/* Make a flattened version of the rangetable for faster access */
+		setup_simple_rel_arrays(root);
+
 		/* We need a trivial path result */
 		*cheapest_path = (Path *)
 			create_result_path((List *) parse->jointree->quals);
@@ -163,7 +166,7 @@ query_planner(PlannerInfo *root, List *tlist,
 	 * rangetable may contain RTEs for rels not actively part of the query,
 	 * for example views.  We don't want to make RelOptInfos for them.
 	 */
-	add_base_rels_to_query(root, (Node *) parse->jointree);
+	add_base_rels_to_query(root, tlist, (Node *) parse->jointree);
 
 	/*
 	 * Examine the targetlist and join tree, adding entries to baserel
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b61005f..2d72244 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,
@@ -3385,7 +3387,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 	setup_simple_rel_arrays(root);
 
 	/* Build RelOptInfo */
-	rel = build_simple_rel(root, 1, RELOPT_BASEREL);
+	rel = build_simple_rel(root, 1, NIL, RELOPT_BASEREL);
 
 	/* Locate IndexOptInfo for the target index */
 	indexInfo = NULL;
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index b91e9f4..61bc447 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -236,7 +236,8 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 		 * used for anything here, but it carries the subroot data structures
 		 * forward to setrefs.c processing.
 		 */
-		rel = build_simple_rel(root, rtr->rtindex, RELOPT_BASEREL);
+		rel = build_simple_rel(root, rtr->rtindex, refnames_tlist,
+							   RELOPT_BASEREL);
 
 		/* plan_params should not be in use in current query level */
 		Assert(root->plan_params == NIL);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 04d5028..7d6bc4b 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -25,6 +25,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/heap.h"
+#include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/clauses.h"
@@ -80,7 +81,7 @@ static List *build_index_tlist(PlannerInfo *root, IndexOptInfo *index,
  */
 void
 get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
-				  RelOptInfo *rel)
+				  List *tlist, RelOptInfo *rel)
 {
 	Index		varno = rel->relid;
 	Relation	relation;
@@ -104,6 +105,30 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 	rel->max_attr = RelationGetNumberOfAttributes(relation);
 	rel->reltablespace = RelationGetForm(relation)->reltablespace;
 
+	/*
+	 * Adjust width of attr_needed slot in case FDW extension wants
+	 * to return pseudo-columns in addition to the columns in its
+	 * table definition.
+	 * GetForeignRelWidth, an optional FDW handler, enables a FDW
+	 * to save properties of a pseudo-column in its private field.
+	 * When the foreign table is the target of UPDATE/DELETE, the query rewriter
+	 * injects a "rowid" pseudo-column to track the remote row to be modified,
+	 * so the FDW has to track which varattno shall perform as "rowid".
+	 */
+	if (RelationGetForm(relation)->relkind == RELKIND_FOREIGN_TABLE)
+	{
+		FdwRoutine *fdwroutine = GetFdwRoutineByRelId(relationObjectId);
+
+		if (fdwroutine->GetForeignRelWidth)
+		{
+			rel->max_attr = fdwroutine->GetForeignRelWidth(root, rel,
+														   relation,
+														   inhparent,
+														   tlist);
+			Assert(rel->max_attr >= RelationGetNumberOfAttributes(relation));
+		}
+	}
+
 	Assert(rel->max_attr >= rel->min_attr);
 	rel->attr_needed = (Relids *)
 		palloc0((rel->max_attr - rel->min_attr + 1) * sizeof(Relids));
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index f724714..84b674c 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -80,7 +80,8 @@ setup_simple_rel_arrays(PlannerInfo *root)
  *	  Construct a new RelOptInfo for a base relation or 'other' relation.
  */
 RelOptInfo *
-build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
+build_simple_rel(PlannerInfo *root, int relid, List *tlist,
+				 RelOptKind reloptkind)
 {
 	RelOptInfo *rel;
 	RangeTblEntry *rte;
@@ -133,7 +134,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
 	{
 		case RTE_RELATION:
 			/* Table --- retrieve statistics from the system catalogs */
-			get_relation_info(root, rte->relid, rte->inh, rel);
+			get_relation_info(root, rte->relid, rte->inh, tlist, rel);
 			break;
 		case RTE_SUBQUERY:
 		case RTE_FUNCTION:
@@ -180,7 +181,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
 			if (appinfo->parent_relid != relid)
 				continue;
 
-			(void) build_simple_rel(root, appinfo->child_relid,
+			(void) build_simple_rel(root, appinfo->child_relid, tlist,
 									RELOPT_OTHER_MEMBER_REL);
 		}
 	}
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 36d95eb..7d4bb99 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -2087,7 +2087,20 @@ get_rte_attribute_name(RangeTblEntry *rte, AttrNumber attnum)
 	 * built (which can easily happen for rules).
 	 */
 	if (rte->rtekind == RTE_RELATION)
-		return get_relid_attribute_name(rte->relid, attnum);
+	{
+		char   *attname = get_attname(rte->relid, attnum);
+
+		if (attname)
+			return attname;
+
+		/*
+		 * XXX - If FDW driver adds pseudo-columns, it may have attribute
+		 * number larger than number of relation's attribute. In this case,
+		 * get_attname() returns NULL and we fall back on alias list on eref.
+		 * It should not happen other than foreign tables.
+		 */
+		Assert(get_rel_relkind(rte->relid) == RELKIND_FOREIGN_TABLE);
+	}
 
 	/*
 	 * Otherwise use the column name from eref.  There should always be one.
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 990ca34..2aa46ee 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1165,7 +1165,10 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 					Relation target_relation)
 {
 	Var		   *var;
-	const char *attrname;
+	List	   *varList;
+	List	   *attNameList;
+	ListCell   *cell1;
+	ListCell   *cell2;
 	TargetEntry *tle;
 
 	if (target_relation->rd_rel->relkind == RELKIND_RELATION)
@@ -1179,8 +1182,35 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 					  -1,
 					  InvalidOid,
 					  0);
+		varList = list_make1(var);
+		attNameList = list_make1("ctid");
+	}
+	else if (target_relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+	{
+		/*
+		 * Emit Rowid so that executor can find the row to update or delete.
+		 */
+		var = makeVar(parsetree->resultRelation,
+					  RelationGetNumberOfAttributes(target_relation) + 1,
+					  CSTRINGOID,
+					  -2,
+					  InvalidOid,
+					  0);
+		varList = list_make1(var);
+
+		/*
+		 * Emit generic record Var so that executor will have the "old" view
+		 * row to pass the RETURNING clause (or upcoming triggers).
+		 */
+		var = makeVar(parsetree->resultRelation,
+					  InvalidAttrNumber,
+					  RECORDOID,
+					  -1,
+					  InvalidOid,
+					  0);
+		varList = lappend(varList, var);
 
-		attrname = "ctid";
+		attNameList = list_make2("rowid", "record");
 	}
 	else
 	{
@@ -1192,16 +1222,21 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 							  parsetree->resultRelation,
 							  0,
 							  false);
-
-		attrname = "wholerow";
+		varList = list_make1(var);
+		attNameList = list_make1("wholerow");
 	}
 
-	tle = makeTargetEntry((Expr *) var,
-						  list_length(parsetree->targetList) + 1,
-						  pstrdup(attrname),
-						  true);
-
-	parsetree->targetList = lappend(parsetree->targetList, tle);
+	/*
+	 * Append them to targetList
+	 */
+	forboth (cell1, varList, cell2, attNameList)
+	{
+		tle = makeTargetEntry((Expr *)lfirst(cell1),
+							  list_length(parsetree->targetList) + 1,
+							  pstrdup((const char *)lfirst(cell2)),
+							  true);
+		parsetree->targetList = lappend(parsetree->targetList, tle);
+	}
 }
 
 
diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
index 721cd25..c6c86c3 100644
--- a/src/include/foreign/fdwapi.h
+++ b/src/include/foreign/fdwapi.h
@@ -14,6 +14,7 @@
 
 #include "nodes/execnodes.h"
 #include "nodes/relation.h"
+#include "utils/rel.h"
 
 /* To avoid including explain.h here, reference ExplainState thus: */
 struct ExplainState;
@@ -22,6 +23,11 @@ struct ExplainState;
 /*
  * Callback function signatures --- see fdwhandler.sgml for more info.
  */
+typedef AttrNumber (*GetForeignRelWidth_function) (PlannerInfo *root,
+												   RelOptInfo *baserel,
+												   Relation foreignrel,
+												   bool inhparent,
+												   List *targetList);
 
 typedef void (*GetForeignRelSize_function) (PlannerInfo *root,
 														RelOptInfo *baserel,
@@ -58,6 +64,24 @@ typedef int (*AcquireSampleRowsFunc) (Relation relation, int elevel,
 typedef bool (*AnalyzeForeignTable_function) (Relation relation,
 												 AcquireSampleRowsFunc *func,
 													BlockNumber *totalpages);
+typedef List *(*PlanForeignModify_function) (PlannerInfo *root,
+											 ModifyTable *plan,
+											 Index resultRelation,
+											 Plan *subplan);
+
+typedef void (*BeginForeignModify_function) (ModifyTableState *mtstate,
+											 ResultRelInfo *rinfo,
+											 List *fdw_private,
+											 Plan *subplan,
+											 int eflags);
+typedef TupleTableSlot *(*ExecForeignInsert_function) (ResultRelInfo *rinfo,
+													   TupleTableSlot *slot);
+typedef TupleTableSlot *(*ExecForeignUpdate_function) (ResultRelInfo *rinfo,
+													   const char *rowid,
+													   TupleTableSlot *slot);
+typedef bool (*ExecForeignDelete_function) (ResultRelInfo *rinfo,
+											const char *rowid);
+typedef void (*EndForeignModify_function) (ResultRelInfo *rinfo);
 
 /*
  * FdwRoutine is the struct returned by a foreign-data wrapper's handler
@@ -90,6 +114,13 @@ typedef struct FdwRoutine
 	 * not provided.
 	 */
 	AnalyzeForeignTable_function AnalyzeForeignTable;
+	GetForeignRelWidth_function GetForeignRelWidth;
+	PlanForeignModify_function PlanForeignModify;
+	BeginForeignModify_function	BeginForeignModify;
+	ExecForeignInsert_function ExecForeignInsert;
+	ExecForeignDelete_function ExecForeignDelete;
+	ExecForeignUpdate_function ExecForeignUpdate;
+	EndForeignModify_function EndForeignModify;
 } FdwRoutine;
 
 
diff --git a/src/include/foreign/foreign.h b/src/include/foreign/foreign.h
index f8aa99e..b63000a 100644
--- a/src/include/foreign/foreign.h
+++ b/src/include/foreign/foreign.h
@@ -14,6 +14,8 @@
 #define FOREIGN_H
 
 #include "nodes/parsenodes.h"
+#include "nodes/plannodes.h"
+#include "nodes/relation.h"
 
 
 /* Helper for obtaining username for user mapping */
@@ -81,4 +83,8 @@ extern List *GetForeignColumnOptions(Oid relid, AttrNumber attnum);
 extern Oid	get_foreign_data_wrapper_oid(const char *fdwname, bool missing_ok);
 extern Oid	get_foreign_server_oid(const char *servername, bool missing_ok);
 
+extern AttrNumber get_pseudo_rowid_column(RelOptInfo *baserel,
+										  List *targetList);
+extern ForeignScan *lookup_foreign_scan_plan(Plan *subplan,
+											 Index rtindex);
 #endif   /* FOREIGN_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index d4911bd..18ab231 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -268,9 +268,11 @@ typedef struct ProjectionInfo
  *						attribute numbers of the "original" tuple and the
  *						attribute numbers of the "clean" tuple.
  *	  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
+ *	  jf_junkRowidNo:	not used by junkfilter code.  Can be used by caller
+ *						to remember the attno used to track a particular tuple
+ *						being updated or deleted.
  *						(execMain.c stores the "ctid" attno here).
+ *	  jf_junkRecordNo:	Also, the attno of whole-row reference.
  * ----------------
  */
 typedef struct JunkFilter
@@ -280,7 +282,8 @@ typedef struct JunkFilter
 	TupleDesc	jf_cleanTupType;
 	AttrNumber *jf_cleanMap;
 	TupleTableSlot *jf_resultSlot;
-	AttrNumber	jf_junkAttNo;
+	AttrNumber	jf_junkRowidNo;
+	AttrNumber	jf_junkRecordNo;
 } JunkFilter;
 
 /* ----------------
@@ -303,6 +306,8 @@ typedef struct JunkFilter
  *		ConstraintExprs			array of constraint-checking expr states
  *		junkFilter				for removing junk attributes from tuples
  *		projectReturning		for computing a RETURNING list
+ *		fdwroutine				FDW callbacks if foreign table
+ *		fdw_state				opaque state of FDW module, or NULL
  * ----------------
  */
 typedef struct ResultRelInfo
@@ -320,6 +325,8 @@ typedef struct ResultRelInfo
 	List	  **ri_ConstraintExprs;
 	JunkFilter *ri_junkFilter;
 	ProjectionInfo *ri_projectReturning;
+	struct FdwRoutine  *ri_fdwroutine;
+	void	   *ri_fdw_state;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index fb9a863..93481aa 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -175,6 +175,7 @@ typedef struct ModifyTable
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
 	int			epqParam;		/* ID of Param for EvalPlanQual re-eval */
+	List	   *fdwPrivList;	/* private fields for foreign tables */
 } ModifyTable;
 
 /* ----------------
@@ -478,6 +479,7 @@ typedef struct ForeignScan
 	List	   *fdw_exprs;		/* expressions that FDW may evaluate */
 	List	   *fdw_private;	/* private data for FDW */
 	bool		fsSystemCol;	/* true if any "system column" is needed */
+	bool		fsPseudoCol;	/* true if any "pseudo column" is needed */
 } ForeignScan;
 
 
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index de889fb..adfc93d 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -133,7 +133,7 @@ extern Path *reparameterize_path(PlannerInfo *root, Path *path,
  */
 extern void setup_simple_rel_arrays(PlannerInfo *root);
 extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid,
-				 RelOptKind reloptkind);
+			   List *tlist, RelOptKind reloptkind);
 extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid);
 extern RelOptInfo *find_join_rel(PlannerInfo *root, Relids relids);
 extern RelOptInfo *build_join_rel(PlannerInfo *root,
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index e0d04db..c5fce41 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -26,7 +26,7 @@ extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook;
 
 
 extern void get_relation_info(PlannerInfo *root, Oid relationObjectId,
-				  bool inhparent, RelOptInfo *rel);
+				  bool inhparent, List *tlist, RelOptInfo *rel);
 
 extern void estimate_rel_size(Relation rel, int32 *attr_widths,
 				  BlockNumber *pages, double *tuples, double *allvisfrac);
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 0fe696c..746a603 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);
@@ -90,7 +91,8 @@ extern bool is_projection_capable_plan(Plan *plan);
 extern int	from_collapse_limit;
 extern int	join_collapse_limit;
 
-extern void add_base_rels_to_query(PlannerInfo *root, Node *jtnode);
+extern void add_base_rels_to_query(PlannerInfo *root, List *tlist,
+								   Node *jtnode);
 extern void build_base_rel_tlists(PlannerInfo *root, List *final_tlist);
 extern void add_vars_to_targetlist(PlannerInfo *root, List *vars,
 					   Relids where_needed, bool create_new_ph);
pgsql-v9.3-writable-fdw-poc.v10.part-2.patchapplication/octet-stream; name=pgsql-v9.3-writable-fdw-poc.v10.part-2.patchDownload
 contrib/postgres_fdw/connection.c              |   70 +-
 contrib/postgres_fdw/connection.h              |    8 +-
 contrib/postgres_fdw/deparse.c                 |  158 ++-
 contrib/postgres_fdw/expected/postgres_fdw.out | 1232 +++++++++++++++++++++++-
 contrib/postgres_fdw/postgres_fdw.c            |  504 +++++++++-
 contrib/postgres_fdw/postgres_fdw.h            |    8 +-
 contrib/postgres_fdw/sql/postgres_fdw.sql      |   31 +
 7 files changed, 1978 insertions(+), 33 deletions(-)

diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index eab8b87..9ca7fbf 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -43,7 +43,7 @@ typedef struct ConnCacheEntry
 	Oid				serverid;	/* oid of foreign server */
 	Oid				userid;		/* oid of local user */
 
-	bool			use_tx;		/* true when using remote transaction */
+	int				conntx;		/* one of PGSQL_FDW_CONNTX_* */
 	int				refs;		/* reference counter */
 	PGconn		   *conn;		/* foreign server connection */
 } ConnCacheEntry;
@@ -65,6 +65,8 @@ cleanup_connection(ResourceReleasePhase phase,
 static PGconn *connect_pg_server(ForeignServer *server, UserMapping *user);
 static void begin_remote_tx(PGconn *conn);
 static void abort_remote_tx(PGconn *conn);
+static void commit_remote_tx(PGconn *conn);
+static void deallocate_remote_prepare(PGconn *conn);
 
 /*
  * Get a PGconn which can be used to execute foreign query on the remote
@@ -80,7 +82,7 @@ static void abort_remote_tx(PGconn *conn);
  * FDW object to invalidate already established connections.
  */
 PGconn *
-GetConnection(ForeignServer *server, UserMapping *user, bool use_tx)
+GetConnection(ForeignServer *server, UserMapping *user, int conntx)
 {
 	bool			found;
 	ConnCacheEntry *entry;
@@ -126,7 +128,7 @@ GetConnection(ForeignServer *server, UserMapping *user, bool use_tx)
 	entry = hash_search(ConnectionHash, &key, HASH_ENTER, &found);
 	if (!found)
 	{
-		entry->use_tx = false;
+		entry->conntx = PGSQL_FDW_CONNTX_NONE;
 		entry->refs = 0;
 		entry->conn = NULL;
 	}
@@ -162,7 +164,7 @@ GetConnection(ForeignServer *server, UserMapping *user, bool use_tx)
 		{
 			/* Clear connection cache entry on error case. */
 			PQfinish(entry->conn);
-			entry->use_tx = false;
+			entry->conntx = PGSQL_FDW_CONNTX_NONE;
 			entry->refs = 0;
 			entry->conn = NULL;
 			PG_RE_THROW();
@@ -182,10 +184,11 @@ GetConnection(ForeignServer *server, UserMapping *user, bool use_tx)
 	 * are in.  We need to remember whether this connection uses remote
 	 * transaction to abort it when this connection is released completely.
 	 */
-	if (use_tx && !entry->use_tx)
+	if (conntx > entry->conntx)
 	{
-		begin_remote_tx(entry->conn);
-		entry->use_tx = use_tx;
+		if (entry->conntx == PGSQL_FDW_CONNTX_NONE)
+			begin_remote_tx(entry->conn);
+		entry->conntx = conntx;
 	}
 
 	return entry->conn;
@@ -355,12 +358,45 @@ abort_remote_tx(PGconn *conn)
 	PQclear(res);
 }
 
+static void
+commit_remote_tx(PGconn *conn)
+{
+	PGresult	   *res;
+
+	elog(DEBUG3, "committing remote transaction");
+
+	res = PQexec(conn, "COMMIT TRANSACTION");
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		PQclear(res);
+		elog(ERROR, "could not commit transaction: %s", PQerrorMessage(conn));
+	}
+	PQclear(res);
+}
+
+static void
+deallocate_remote_prepare(PGconn *conn)
+{
+	PGresult	   *res;
+
+	elog(DEBUG3, "deallocating remote prepares");
+
+	res = PQexec(conn, "DEALLOCATE PREPARE ALL");
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		PQclear(res);
+		elog(ERROR, "could not deallocate prepared statement: %s",
+			 PQerrorMessage(conn));
+	}
+	PQclear(res);
+}
+
 /*
  * Mark the connection as "unused", and close it if the caller was the last
  * user of the connection.
  */
 void
-ReleaseConnection(PGconn *conn)
+ReleaseConnection(PGconn *conn, bool is_abort)
 {
 	HASH_SEQ_STATUS		scan;
 	ConnCacheEntry	   *entry;
@@ -412,7 +448,7 @@ ReleaseConnection(PGconn *conn)
 			 PQtransactionStatus(conn) == PQTRANS_INERROR ? "INERROR" :
 			 "UNKNOWN");
 		PQfinish(conn);
-		entry->use_tx = false;
+		entry->conntx = PGSQL_FDW_CONNTX_NONE;
 		entry->refs = 0;
 		entry->conn = NULL;
 		return;
@@ -430,10 +466,16 @@ ReleaseConnection(PGconn *conn)
 	 * If this connection uses remote transaction and there is no user other
 	 * than the caller, abort the remote transaction and forget about it.
 	 */
-	if (entry->use_tx && entry->refs == 0)
+	if (entry->conntx > PGSQL_FDW_CONNTX_NONE && entry->refs == 0)
 	{
-		abort_remote_tx(conn);
-		entry->use_tx = false;
+		if (entry->conntx > PGSQL_FDW_CONNTX_READ_ONLY)
+			deallocate_remote_prepare(conn);
+		if (is_abort || entry->conntx == PGSQL_FDW_CONNTX_READ_ONLY)
+			abort_remote_tx(conn);
+		else
+			commit_remote_tx(conn);
+
+		entry->conntx = PGSQL_FDW_CONNTX_NONE;
 	}
 }
 
@@ -485,7 +527,7 @@ cleanup_connection(ResourceReleasePhase phase,
 		elog(DEBUG3, "discard postgres_fdw connection %p due to resowner cleanup",
 			 entry->conn);
 		PQfinish(entry->conn);
-		entry->use_tx = false;
+		entry->conntx = PGSQL_FDW_CONNTX_NONE;
 		entry->refs = 0;
 		entry->conn = NULL;
 	}
@@ -597,7 +639,7 @@ postgres_fdw_disconnect(PG_FUNCTION_ARGS)
 
 	/* Discard cached connection, and clear reference counter. */
 	PQfinish(entry->conn);
-	entry->use_tx = false;
+	entry->conntx = PGSQL_FDW_CONNTX_NONE;
 	entry->refs = 0;
 	entry->conn = NULL;
 
diff --git a/contrib/postgres_fdw/connection.h b/contrib/postgres_fdw/connection.h
index 4c9d850..f97cc8a 100644
--- a/contrib/postgres_fdw/connection.h
+++ b/contrib/postgres_fdw/connection.h
@@ -16,10 +16,14 @@
 #include "foreign/foreign.h"
 #include "libpq-fe.h"
 
+#define PGSQL_FDW_CONNTX_NONE			0
+#define PGSQL_FDW_CONNTX_READ_ONLY		1
+#define PGSQL_FDW_CONNTX_READ_WRITE		2
+
 /*
  * Connection management
  */
-PGconn *GetConnection(ForeignServer *server, UserMapping *user, bool use_tx);
-void ReleaseConnection(PGconn *conn);
+PGconn *GetConnection(ForeignServer *server, UserMapping *user, int conntx);
+void ReleaseConnection(PGconn *conn, bool is_abort);
 
 #endif /* CONNECTION_H */
diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
index 69e6a3e..b609a57 100644
--- a/contrib/postgres_fdw/deparse.c
+++ b/contrib/postgres_fdw/deparse.c
@@ -12,6 +12,7 @@
  */
 #include "postgres.h"
 
+#include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/transam.h"
 #include "catalog/pg_class.h"
@@ -86,9 +87,11 @@ void
 deparseSimpleSql(StringInfo buf,
 				 PlannerInfo *root,
 				 RelOptInfo *baserel,
-				 List *local_conds)
+				 List *local_conds,
+				 AttrNumber anum_rowid)
 {
 	RangeTblEntry *rte;
+	Relation	rel;
 	ListCell   *lc;
 	StringInfoData	foreign_relname;
 	bool		first;
@@ -125,6 +128,24 @@ deparseSimpleSql(StringInfo buf,
 	}
 
 	/*
+	 * XXX - When this foreign table is target relation and RETURNING
+	 * clause reference some column, we have to mark these columns as
+	 * in-use. It is needed to support DELETE command, because INSERT
+	 * and UPDATE implicitly add references to all the regular columns
+	 * on baserel->reltargetlist.
+	 */
+	if (root->parse->resultRelation == baserel->relid &&
+		root->parse->returningList)
+	{
+		List   *attrs;
+
+		attrs = pull_var_clause((Node *) root->parse->returningList,
+								PVC_RECURSE_AGGREGATES,
+                                PVC_RECURSE_PLACEHOLDERS);
+		attr_used = list_union(attr_used, attrs);
+	}
+
+	/*
 	 * deparse SELECT clause
 	 *
 	 * List attributes which are in either target list or local restriction.
@@ -136,9 +157,10 @@ deparseSimpleSql(StringInfo buf,
 	 */
 	appendStringInfo(buf, "SELECT ");
 	rte = root->simple_rte_array[baserel->relid];
+	rel = heap_open(rte->relid, NoLock);
 	attr_used = list_union(attr_used, baserel->reltargetlist);
 	first = true;
-	for (attr = 1; attr <= baserel->max_attr; attr++)
+	for (attr = 1; attr <= RelationGetNumberOfAttributes(rel); attr++)
 	{
 		Var		   *var = NULL;
 		ListCell   *lc;
@@ -167,6 +189,10 @@ deparseSimpleSql(StringInfo buf,
 		else
 			appendStringInfo(buf, "NULL");
 	}
+	if (anum_rowid != InvalidAttrNumber)
+		appendStringInfo(buf, "%sctid", (first ? "" : ","));
+
+	heap_close(rel, NoLock);
 	appendStringInfoChar(buf, ' ');
 
 	/*
@@ -283,6 +309,134 @@ deparseAnalyzeSql(StringInfo buf, Relation rel)
 }
 
 /*
+ * deparse RETURNING clause of INSERT/UPDATE/DELETE
+ */
+static void
+deparseReturningSql(StringInfo buf, PlannerInfo *root, Index rtindex,
+					Relation frel)
+{
+	AttrNumber	i, nattrs = RelationGetNumberOfAttributes(frel);
+
+	appendStringInfo(buf, " RETURNING ");
+	for (i=0; i < nattrs; i++)
+	{
+		Form_pg_attribute attr = RelationGetDescr(frel)->attrs[i];
+
+		if (i > 0)
+			appendStringInfo(buf, ",");
+
+		if (attr->attisdropped)
+			appendStringInfo(buf, "null");
+		else
+		{
+			Var		var;
+
+			var.varno = rtindex;
+			var.varattno = attr->attnum;
+			deparseVar(buf, &var, root);
+		}
+	}
+}
+
+/*
+ * deparse remote INSERT statement
+ */
+void
+deparseInsertSql(StringInfo buf, PlannerInfo *root, Index rtindex,
+				 List *targetAttrs, bool has_returning)
+{
+	RangeTblEntry  *rte = root->simple_rte_array[rtindex];
+	Relation		frel = heap_open(rte->relid, NoLock);
+	ListCell	   *lc;
+	AttrNumber		pindex = 1;
+
+	appendStringInfo(buf, "INSERT INTO ");
+	deparseRelation(buf, rte);
+	appendStringInfo(buf, "(");
+
+	foreach (lc, targetAttrs)
+	{
+		Var		var;
+		Form_pg_attribute	attr
+			= RelationGetDescr(frel)->attrs[lfirst_int(lc) - 1];
+
+		Assert(!attr->attisdropped);
+		if (lc != list_head(targetAttrs))
+			appendStringInfo(buf, ",");
+
+		var.varno = rtindex;
+		var.varattno = attr->attnum;
+		deparseVar(buf, &var, root);
+	}
+	appendStringInfo(buf, ") VALUES (");
+
+	foreach (lc, targetAttrs)
+	{
+		appendStringInfo(buf, "%s$%d", (pindex == 1 ? "" : ","), pindex);
+		pindex++;
+	}
+	appendStringInfo(buf, ")");
+
+	if (has_returning)
+		deparseReturningSql(buf, root, rtindex, frel);
+
+	heap_close(frel, NoLock);
+}
+
+/*
+ * deparse remote UPDATE statement
+ */
+void
+deparseUpdateSql(StringInfo buf, PlannerInfo *root, Index rtindex,
+				 List *targetAttrs, bool has_returning)
+{
+	RangeTblEntry  *rte = root->simple_rte_array[rtindex];
+	Relation		frel = heap_open(rte->relid, NoLock);
+	ListCell	   *lc;
+	AttrNumber		pindex = 2;
+
+	appendStringInfo(buf, "UPDATE ");
+	deparseRelation(buf, rte);
+	appendStringInfo(buf, " SET ");
+
+	foreach (lc, targetAttrs)
+	{
+		Var		var;
+		Form_pg_attribute	attr
+			= RelationGetDescr(frel)->attrs[lfirst_int(lc) - 1];
+
+		Assert(!attr->attisdropped);
+
+		if (lc != list_head(targetAttrs))
+			appendStringInfo(buf, ",");
+
+		var.varno = rtindex;
+		var.varattno = attr->attnum;
+		deparseVar(buf, &var, root);
+		appendStringInfo(buf, "=$%d", pindex++);
+	}
+	appendStringInfo(buf, " WHERE ctid=$1");
+
+	if (has_returning)
+		deparseReturningSql(buf, root, rtindex, frel);
+
+	heap_close(frel, NoLock);
+}
+
+/*
+ * deparse remote DELETE statement
+ */
+void
+deparseDeleteSql(StringInfo buf, PlannerInfo *root, Index rtindex)
+{
+	RangeTblEntry  *rte = root->simple_rte_array[rtindex];
+
+	appendStringInfo(buf, "DELETE FROM ");
+	deparseRelation(buf, rte);
+	appendStringInfo(buf, " WHERE ctid = $1");
+}
+
+/*
  * Deparse given expression into buf.  Actual string operation is delegated to
  * node-type-specific functions.
  *
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index f81c727..131617b 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -736,15 +736,1245 @@ SELECT srvname FROM postgres_fdw_connections;
 (0 rows)
 
 -- ===================================================================
+-- test for writable foreign table stuff (PoC stage now)
+-- ===================================================================
+EXPLAIN(verbose) INSERT INTO ft2 (c1,c2,c3) (SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20);
+                                                                                                          QUERY PLAN                                                                                                           
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Insert on public.ft2  (cost=100.00..100.03 rows=1 width=40)
+   ->  Subquery Scan on "*SELECT*"  (cost=100.00..100.03 rows=1 width=40)
+         Output: NULL::integer, "*SELECT*"."?column?", "*SELECT*"."?column?", "*SELECT*"."?column?", NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying, NULL::bpchar, NULL::user_enum
+         ->  Limit  (cost=100.00..100.02 rows=1 width=40)
+               Output: ((ft2_1.c1 + 1000)), ((ft2_1.c2 + 100)), ((ft2_1.c3 || ft2_1.c3))
+               ->  Foreign Scan on public.ft2 ft2_1  (cost=100.00..100.02 rows=1 width=40)
+                     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"
+(8 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) 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  (cost=200.00..200.04 rows=1 width=344)
+   ->  Nested Loop  (cost=200.00..200.04 rows=1 width=344)
+         Output: NULL::integer, ft2.c1, (ft2.c2 + 500), (ft2.c3 || '_update9'::text), ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid, ft2.*, ft1.*
+         Join Filter: (ft2.c2 = ft1.c1)
+         ->  Foreign Scan on public.ft2  (cost=100.00..100.01 rows=1 width=232)
+               Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid, ft2.*
+               Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8,ctid FROM "S 1"."T 1" FOR UPDATE
+         ->  Foreign Scan on public.ft1  (cost=100.00..100.01 rows=1 width=116)
+               Output: ft1.*, ft1.c1
+               Remote SQL: SELECT "C 1", NULL, NULL, NULL, NULL, NULL, NULL, NULL FROM "S 1"."T 1" WHERE ((("C 1" OPERATOR(pg_catalog.%) 10) OPERATOR(pg_catalog.=) 9))
+(10 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) DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2;
+                                                                               QUERY PLAN                                                                               
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Delete on public.ft2  (cost=200.00..200.03 rows=1 width=256)
+   ->  Nested Loop  (cost=200.00..200.03 rows=1 width=256)
+         Output: ft2.ctid, ft2.*, ft1.*
+         Join Filter: (ft2.c2 = ft1.c1)
+         ->  Foreign Scan on public.ft2  (cost=100.00..100.01 rows=1 width=148)
+               Output: ft2.ctid, ft2.*, ft2.c2
+               Remote SQL: SELECT NULL, c2, NULL, NULL, NULL, NULL, NULL, NULL,ctid FROM "S 1"."T 1" FOR UPDATE
+         ->  Foreign Scan on public.ft1  (cost=100.00..100.01 rows=1 width=116)
+               Output: ft1.*, ft1.c1
+               Remote SQL: SELECT "C 1", NULL, NULL, NULL, NULL, NULL, NULL, NULL FROM "S 1"."T 1" WHERE ((("C 1" OPERATOR(pg_catalog.%) 10) OPERATOR(pg_catalog.=) 2))
+(10 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)
+
+-- In case of remote table has before-row trigger or default with returning
+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)
+
+-- ===================================================================
 -- cleanup
 -- ===================================================================
 DROP OPERATOR === (int, int) CASCADE;
 DROP OPERATOR !== (int, int) CASCADE;
 DROP FUNCTION postgres_fdw_abs(int);
 DROP SCHEMA "S 1" CASCADE;
-NOTICE:  drop cascades to 2 other objects
+NOTICE:  drop cascades to 3 other objects
 DETAIL:  drop cascades to table "S 1"."T 1"
 drop cascades to table "S 1"."T 2"
+drop cascades to function "S 1".f_brtrig()
 DROP TYPE user_enum CASCADE;
 NOTICE:  drop cascades to 2 other objects
 DETAIL:  drop cascades to foreign table ft2 column c8
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 6b870ab..420bb4d 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -14,6 +14,7 @@
 #include "fmgr.h"
 
 #include "access/htup_details.h"
+#include "access/sysattr.h"
 #include "catalog/pg_foreign_server.h"
 #include "catalog/pg_foreign_table.h"
 #include "catalog/pg_type.h"
@@ -27,6 +28,7 @@
 #include "optimizer/pathnode.h"
 #include "optimizer/planmain.h"
 #include "optimizer/restrictinfo.h"
+#include "parser/parsetree.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -59,6 +61,7 @@ typedef struct PostgresFdwPlanState {
 	List		   *param_conds;
 	List		   *local_conds;
 	int				width;			/* obtained by remote EXPLAIN */
+	AttrNumber		anum_rowid;
 
 	/* Cached catalog information. */
 	ForeignTable   *table;
@@ -150,6 +153,24 @@ typedef struct PostgresAnalyzeState
 } PostgresAnalyzeState;
 
 /*
+ * Describes a state of modify request for a foreign table
+ */
+typedef struct PostgresFdwModifyState
+{
+	PGconn	   *conn;
+	char	   *query;
+	bool		has_returning;
+	List	   *target_attrs;
+	char	   *p_name;
+	int			p_nums;
+	Oid		   *p_types;
+	FmgrInfo   *p_flinfo;
+	Oid		   *r_ioparam;
+	FmgrInfo   *r_flinfo;
+	MemoryContext	es_query_cxt;
+} PostgresFdwModifyState;
+
+/*
  * SQL functions
  */
 extern Datum postgres_fdw_handler(PG_FUNCTION_ARGS);
@@ -158,6 +179,11 @@ PG_FUNCTION_INFO_V1(postgres_fdw_handler);
 /*
  * FDW callback routines
  */
+static AttrNumber postgresGetForeignRelWidth(PlannerInfo *root,
+											 RelOptInfo *baserel,
+											 Relation foreignrel,
+											 bool inhparent,
+											 List *targetList);
 static void postgresGetForeignRelSize(PlannerInfo *root,
 									  RelOptInfo *baserel,
 									  Oid foreigntableid);
@@ -179,6 +205,23 @@ static void postgresEndForeignScan(ForeignScanState *node);
 static bool postgresAnalyzeForeignTable(Relation relation,
 										AcquireSampleRowsFunc *func,
 										BlockNumber *totalpages);
+static List *postgresPlanForeignModify(PlannerInfo *root,
+									   ModifyTable *plan,
+									   Index resultRelation,
+									   Plan *subplan);
+static void postgresBeginForeignModify(ModifyTableState *mtstate,
+									   ResultRelInfo *resultRelInfo,
+									   List *fdw_private,
+									   Plan *subplan,
+									   int eflags);
+static TupleTableSlot *postgresExecForeignInsert(ResultRelInfo *rinfo,
+												 TupleTableSlot *slot);
+static bool postgresExecForeignDelete(ResultRelInfo *rinfo,
+									  const char *rowid);
+static TupleTableSlot * postgresExecForeignUpdate(ResultRelInfo *rinfo,
+												  const char *rowid,
+												  TupleTableSlot *slot);
+static void postgresEndForeignModify(ResultRelInfo *rinfo);
 
 /*
  * Helper functions
@@ -231,6 +274,7 @@ postgres_fdw_handler(PG_FUNCTION_ARGS)
 	FdwRoutine	*routine = makeNode(FdwRoutine);
 
 	/* Required handler functions. */
+	routine->GetForeignRelWidth = postgresGetForeignRelWidth;
 	routine->GetForeignRelSize = postgresGetForeignRelSize;
 	routine->GetForeignPaths = postgresGetForeignPaths;
 	routine->GetForeignPlan = postgresGetForeignPlan;
@@ -239,6 +283,12 @@ postgres_fdw_handler(PG_FUNCTION_ARGS)
 	routine->IterateForeignScan = postgresIterateForeignScan;
 	routine->ReScanForeignScan = postgresReScanForeignScan;
 	routine->EndForeignScan = postgresEndForeignScan;
+	routine->PlanForeignModify = postgresPlanForeignModify;
+	routine->BeginForeignModify = postgresBeginForeignModify;
+	routine->ExecForeignInsert = postgresExecForeignInsert;
+	routine->ExecForeignDelete = postgresExecForeignDelete;
+	routine->ExecForeignUpdate = postgresExecForeignUpdate;
+	routine->EndForeignModify = postgresEndForeignModify;
 
 	/* Optional handler functions. */
 	routine->AnalyzeForeignTable = postgresAnalyzeForeignTable;
@@ -247,6 +297,34 @@ postgres_fdw_handler(PG_FUNCTION_ARGS)
 }
 
 /*
+ * postgresGetForeignRelWidth
+ *		Informs how many columns (including pseudo ones) are needed.
+ */
+static AttrNumber
+postgresGetForeignRelWidth(PlannerInfo *root,
+						   RelOptInfo *baserel,
+						   Relation foreignrel,
+						   bool inhparent,
+						   List *targetList)
+{
+	PostgresFdwPlanState *fpstate = palloc0(sizeof(PostgresFdwPlanState));
+
+	baserel->fdw_private = fpstate;
+
+	/* does rowid pseudo-column is required? */
+	fpstate->anum_rowid = get_pseudo_rowid_column(baserel, targetList);
+	if (fpstate->anum_rowid != InvalidAttrNumber)
+	{
+		RangeTblEntry *rte = rt_fetch(baserel->relid,
+									  root->parse->rtable);
+		rte->eref->colnames = lappend(rte->eref->colnames,
+									  makeString("ctid"));
+		return fpstate->anum_rowid;
+	}
+	return RelationGetNumberOfAttributes(foreignrel);
+}
+
+/*
  * postgresGetForeignRelSize
  *		Estimate # of rows and width of the result of the scan
  *
@@ -283,7 +361,7 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	 * We use PostgresFdwPlanState to pass various information to subsequent
 	 * functions.
 	 */
-	fpstate = palloc0(sizeof(PostgresFdwPlanState));
+	fpstate = baserel->fdw_private;
 	initStringInfo(&fpstate->sql);
 	sql = &fpstate->sql;
 
@@ -320,10 +398,9 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	 */
 	classifyConditions(root, baserel, &remote_conds, &param_conds,
 					   &local_conds);
-	deparseSimpleSql(sql, root, baserel, local_conds);
+	deparseSimpleSql(sql, root, baserel, local_conds, fpstate->anum_rowid);
 	if (list_length(remote_conds) > 0)
 		appendWhereClause(sql, true, remote_conds, root);
-	elog(DEBUG3, "Query SQL: %s", sql->data);
 
 	/*
 	 * If the table or the server is configured to use remote EXPLAIN, connect
@@ -337,10 +414,10 @@ postgresGetForeignRelSize(PlannerInfo *root,
 		PGconn		   *conn;
 
 		user = GetUserMapping(GetOuterUserId(), server->serverid);
-		conn = GetConnection(server, user, false);
+		conn = GetConnection(server, user, PGSQL_FDW_CONNTX_NONE);
 		get_remote_estimate(sql->data, conn, &rows, &width,
 							&startup_cost, &total_cost);
-		ReleaseConnection(conn);
+		ReleaseConnection(conn, false);
 
 		/*
 		 * Estimate selectivity of conditions which are not used in remote
@@ -391,7 +468,6 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	fpstate->width = width;
 	fpstate->table = table;
 	fpstate->server = server;
-	baserel->fdw_private = (void *) fpstate;
 }
 
 /*
@@ -592,7 +668,7 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 	table = GetForeignTable(relid);
 	server = GetForeignServer(table->serverid);
 	user = GetUserMapping(GetOuterUserId(), server->serverid);
-	conn = GetConnection(server, user, true);
+	conn = GetConnection(server, user, PGSQL_FDW_CONNTX_READ_ONLY);
 	festate->conn = conn;
 
 	/* Result will be filled in first Iterate call. */
@@ -724,7 +800,7 @@ postgresEndForeignScan(ForeignScanState *node)
 	 * end of the scan to make the lifespan of remote transaction same as the
 	 * local query.
 	 */
-	ReleaseConnection(festate->conn);
+	ReleaseConnection(festate->conn, false);
 	festate->conn = NULL;
 
 	/* Discard fetch results */
@@ -790,7 +866,7 @@ get_remote_estimate(const char *sql, PGconn *conn,
 		PQclear(res);
 
 		/* Release connection and let connection manager cleanup. */
-		ReleaseConnection(conn);
+		ReleaseConnection(conn, true);
 
 		PG_RE_THROW();
 	}
@@ -947,7 +1023,7 @@ execute_query(ForeignScanState *node)
 		PQclear(res);
 
 		/* Release connection and let connection manager cleanup. */
-		ReleaseConnection(conn);
+		ReleaseConnection(conn, true);
 
 		/* propagate error */
 		PG_RE_THROW();
@@ -1105,6 +1181,8 @@ postgres_fdw_error_callback(void *arg)
 
 	relname = get_rel_name(errpos->relid);
 	colname = get_attname(errpos->relid, errpos->cur_attno);
+	if (!colname)
+		colname = "pseudo-column";
 	errcontext("column %s of foreign table %s",
 			   quote_identifier(colname), quote_identifier(relname));
 }
@@ -1172,7 +1250,7 @@ postgresAcquireSampleRowsFunc(Relation relation, int elevel,
 	table = GetForeignTable(relation->rd_id);
 	server = GetForeignServer(table->serverid);
 	user = GetUserMapping(GetOuterUserId(), server->serverid);
-	conn = GetConnection(server, user, true);
+	conn = GetConnection(server, user, PGSQL_FDW_CONNTX_READ_ONLY);
 
 	/*
 	 * Acquire sample rows from the result set.
@@ -1239,13 +1317,13 @@ postgresAcquireSampleRowsFunc(Relation relation, int elevel,
 		PQclear(res);
 
 		/* Release connection and let connection manager cleanup. */
-		ReleaseConnection(conn);
+		ReleaseConnection(conn, true);
 
 		PG_RE_THROW();
 	}
 	PG_END_TRY();
 
-	ReleaseConnection(conn);
+	ReleaseConnection(conn, false);
 
 	/* We assume that we have no dead tuple. */
 	*totaldeadrows = 0.0;
@@ -1429,3 +1507,403 @@ analyze_row_processor(PGresult *res, PostgresAnalyzeState *astate, bool first)
 
 	return;
 }
+
+static List *
+postgresPlanForeignModify(PlannerInfo *root,
+						  ModifyTable *plan,
+						  Index resultRelation,
+						  Plan *subplan)
+{
+	CmdType			operation = plan->operation;
+	StringInfoData	sql;
+	List		   *targetAttrs = NIL;
+	bool			has_returning = (!!plan->returningLists);
+
+	initStringInfo(&sql);
+
+	/*
+	 * XXX - In case of UPDATE or DELETE commands are quite "simple",
+	 * we will be able to execute raw UPDATE or DELETE statement at
+	 * the stage of scan, instead of combination SELECT ... FOR UPDATE
+	 * and either of UPDATE or DELETE commands.
+	 * It should be an idea of optimization in the future version.
+	 *
+	 * XXX - FOR UPDATE should be appended on the remote query of scan
+	 * stage to avoid unexpected concurrent update on the target rows.
+	 */
+	if (operation == CMD_UPDATE || operation == CMD_DELETE)
+	{
+		ForeignScan	   *fscan;
+		Value		   *select_sql;
+
+		fscan = lookup_foreign_scan_plan(subplan, resultRelation);
+		if (!fscan)
+			elog(ERROR, "no underlying scan plan found in subplan tree");
+
+		select_sql = list_nth(fscan->fdw_private,
+							  FdwPrivateSelectSql);
+		appendStringInfo(&sql, "%s FOR UPDATE", strVal(select_sql));
+		strVal(select_sql) = pstrdup(sql.data);
+
+		resetStringInfo(&sql);
+	}
+
+	/*
+	 * XXX - In case of INSERT or UPDATE commands, it needs to list up
+	 * columns to be updated or inserted for performance optimization
+	 * and consistent behavior when DEFAULT is set on the remote table.
+	 */
+	if (operation == CMD_INSERT || operation == CMD_UPDATE)
+	{
+		RangeTblEntry  *rte = rt_fetch(resultRelation, root->parse->rtable);
+		Bitmapset	   *tmpset = bms_copy(rte->modifiedCols);
+		AttrNumber		col;
+
+		while ((col = bms_first_member(tmpset)) >= 0)
+		{
+			col += FirstLowInvalidHeapAttributeNumber;
+			if (col <= InvalidAttrNumber)
+				elog(ERROR, "system-column update is not supported");
+			targetAttrs = lappend_int(targetAttrs, col);
+		}
+	}
+
+	switch (operation)
+	{
+		case CMD_INSERT:
+			deparseInsertSql(&sql, root, resultRelation,
+							 targetAttrs, has_returning);
+			elog(DEBUG3, "Remote INSERT query: %s", sql.data);
+			break;
+		case CMD_UPDATE:
+			deparseUpdateSql(&sql, root, resultRelation,
+							 targetAttrs, has_returning);
+			elog(DEBUG3, "Remote UPDATE query: %s", sql.data);
+			break;
+		case CMD_DELETE:
+			deparseDeleteSql(&sql, root, resultRelation);
+			elog(DEBUG3, "Remote DELETE query: %s", sql.data);
+			break;
+		default:
+			elog(ERROR, "unexpected operation: %d", (int) operation);
+	}
+	return list_make3(makeString(sql.data),
+					  makeInteger(has_returning),
+					  targetAttrs);
+}
+
+static void
+postgresBeginForeignModify(ModifyTableState *mtstate,
+						   ResultRelInfo *resultRelInfo,
+						   List *fdw_private,
+						   Plan *subplan,
+						   int eflags)
+{
+	PostgresFdwModifyState *fmstate;
+	CmdType			operation = mtstate->operation;
+	Relation		frel = resultRelInfo->ri_RelationDesc;
+	AttrNumber		n_params;
+	ListCell	   *lc;
+	ForeignTable   *ftable;
+	ForeignServer  *fserver;
+	UserMapping	   *fuser;
+	Oid				typefnoid;
+	bool			isvarlena;
+
+	/*
+	 * Construct PostgresFdwExecutionState
+	 */
+	fmstate = palloc0(sizeof(PostgresFdwExecutionState));
+
+	ftable = GetForeignTable(RelationGetRelid(frel));
+	fserver = GetForeignServer(ftable->serverid);
+	fuser = GetUserMapping(GetOuterUserId(), fserver->serverid);
+
+	fmstate->query = strVal(linitial(fdw_private));
+	fmstate->has_returning = intVal(lsecond(fdw_private));
+	fmstate->target_attrs = lthird(fdw_private);
+	fmstate->conn = GetConnection(fserver, fuser,
+								  PGSQL_FDW_CONNTX_READ_WRITE);
+	n_params = list_length(fmstate->target_attrs) + 1;
+	fmstate->p_name = NULL;
+	fmstate->p_types = palloc0(sizeof(Oid) * n_params);
+	fmstate->p_flinfo = palloc0(sizeof(FmgrInfo) * n_params);
+
+	/* 1st parameter should be ctid on UPDATE or DELETE */
+	if (operation == CMD_UPDATE || operation == CMD_DELETE)
+	{
+		fmstate->p_types[fmstate->p_nums] = TIDOID;
+		getTypeOutputInfo(TIDOID, &typefnoid, &isvarlena);
+		fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
+		fmstate->p_nums++;
+	}
+	/* following parameters should be regular columns */
+	if (operation == CMD_UPDATE || operation == CMD_INSERT)
+	{
+		foreach (lc, fmstate->target_attrs)
+		{
+			Form_pg_attribute attr
+				= RelationGetDescr(frel)->attrs[lfirst_int(lc) - 1];
+
+			Assert(!attr->attisdropped);
+
+			fmstate->p_types[fmstate->p_nums] = attr->atttypid;
+			getTypeOutputInfo(attr->atttypid, &typefnoid, &isvarlena);
+			fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
+			fmstate->p_nums++;
+		}
+	}
+	Assert(fmstate->p_nums <= n_params);
+
+	/* input handlers for returning clause */
+	if (fmstate->has_returning)
+	{
+		AttrNumber	i, nattrs = RelationGetNumberOfAttributes(frel);
+
+		fmstate->r_ioparam = palloc0(sizeof(Oid) * nattrs);
+		fmstate->r_flinfo = palloc0(sizeof(FmgrInfo) * nattrs);
+		for (i=0; i < nattrs; i++)
+		{
+			Form_pg_attribute attr = RelationGetDescr(frel)->attrs[i];
+
+			if (attr->attisdropped)
+				continue;
+
+			getTypeInputInfo(attr->atttypid, &typefnoid,
+							 &fmstate->r_ioparam[i]);
+			fmgr_info(typefnoid, &fmstate->r_flinfo[i]);
+		}
+	}
+	fmstate->es_query_cxt = mtstate->ps.state->es_query_cxt;
+	resultRelInfo->ri_fdw_state = fmstate;
+}
+
+static void
+prepare_foreign_modify(PostgresFdwModifyState *fmstate)
+{
+	static int	prep_id = 1;
+	char		prep_name[NAMEDATALEN];
+	PGresult   *res;
+
+	snprintf(prep_name, sizeof(prep_name),
+			 "pgsql_fdw_prep_%08x", prep_id++);
+
+	res = PQprepare(fmstate->conn,
+					prep_name,
+					fmstate->query,
+					fmstate->p_nums,
+					fmstate->p_types);
+	if (!res || PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		PQclear(res);
+		elog(ERROR, "could not prepare statement (%s): %s",
+			 fmstate->query, PQerrorMessage(fmstate->conn));
+	}
+	PQclear(res);
+
+	fmstate->p_name = MemoryContextStrdup(fmstate->es_query_cxt, prep_name);
+}
+
+static int
+setup_exec_prepared(ResultRelInfo *resultRelInfo,
+					const char *rowid, TupleTableSlot *slot,
+					const char *p_values[], int p_lengths[])
+{
+	PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state;
+	int			pindex = 0;
+
+	/* 1st parameter should be ctid */
+	if (rowid)
+	{
+		p_values[pindex] = rowid;
+		p_lengths[pindex] = strlen(rowid) + 1;
+		pindex++;
+	}
+
+	/* following parameters are as TupleDesc */
+	if (slot != NULL)
+	{
+		TupleDesc	tupdesc = slot->tts_tupleDescriptor;
+		ListCell   *lc;
+
+		foreach (lc, fmstate->target_attrs)
+		{
+			Form_pg_attribute	attr = tupdesc->attrs[lfirst_int(lc) - 1];
+			Datum		value;
+			bool		isnull;
+
+			Assert(!attr->attisdropped);
+
+			value = slot_getattr(slot, attr->attnum, &isnull);
+			if (isnull)
+			{
+				p_values[pindex] = NULL;
+				p_lengths[pindex] = 0;
+			}
+			else
+			{
+				p_values[pindex] =
+					OutputFunctionCall(&fmstate->p_flinfo[pindex], value);
+				p_lengths[pindex] = strlen(p_values[pindex]) + 1;
+			}
+			pindex++;
+		}
+	}
+	return pindex;
+}
+
+static void
+store_returning_result(PostgresFdwModifyState *fmstate,
+					   TupleTableSlot *slot, PGresult *res)
+{
+	TupleDesc	tupdesc = slot->tts_tupleDescriptor;
+	AttrNumber	i, nattrs = tupdesc->natts;
+	Datum	   *values = alloca(sizeof(Datum) * nattrs);
+	bool	   *isnull = alloca(sizeof(bool) * nattrs);
+	HeapTuple	newtup;
+
+	memset(values, 0, sizeof(Datum) * nattrs);
+	memset(isnull, 0, sizeof(bool) * nattrs);
+
+	for (i=0; i < nattrs; i++)
+	{
+		Form_pg_attribute	attr = tupdesc->attrs[i];
+
+		if (attr->attisdropped || PQgetisnull(res, 0, i))
+			isnull[i] = true;
+		else
+		{
+			//elog(INFO, "col %d %s %d: value: %s fnoid: %u", i, NameStr(attr->attname), attr->attisdropped, PQgetvalue(res, 0, i), fmstate->r_flinfo[i].fn_oid);
+			values[i] = InputFunctionCall(&fmstate->r_flinfo[i],
+										  PQgetvalue(res, 0, i),
+										  fmstate->r_ioparam[i],
+										  attr->atttypmod);
+		}
+	}
+	newtup = heap_form_tuple(tupdesc, values, isnull);
+	ExecStoreTuple(newtup, slot, InvalidBuffer, false);
+}
+
+static TupleTableSlot *
+postgresExecForeignInsert(ResultRelInfo *resultRelInfo,
+						  TupleTableSlot *slot)
+{
+	PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state;
+	const char	  **p_values  = alloca(sizeof(char *) * fmstate->p_nums);
+	int			   *p_lengths = alloca(sizeof(int) * fmstate->p_nums);
+	AttrNumber		nattrs;
+	PGresult	   *res;
+	int				n_rows;
+
+	if (!fmstate->p_name)
+		prepare_foreign_modify(fmstate);
+
+	nattrs = setup_exec_prepared(resultRelInfo,
+								 NULL, slot,
+								 p_values, p_lengths);
+	Assert(fmstate->p_nums == nattrs);
+
+	res = PQexecPrepared(fmstate->conn,
+						 fmstate->p_name,
+						 nattrs,
+						 p_values,
+						 p_lengths,
+						 NULL, 0);
+	if (!res || (!fmstate->has_returning ?
+				 PQresultStatus(res) != PGRES_COMMAND_OK :
+				 PQresultStatus(res) != PGRES_TUPLES_OK))
+		elog(ERROR, "could not execute prepared statement (%s): %s",
+			 fmstate->query, PQerrorMessage(fmstate->conn));
+	n_rows = atoi(PQcmdTuples(res));
+	if (n_rows > 0 && fmstate->has_returning)
+		store_returning_result(fmstate, slot, res);
+	PQclear(res);
+
+	return (n_rows > 0 ? slot : NULL);
+}
+
+static bool
+postgresExecForeignDelete(ResultRelInfo *resultRelInfo, const char *rowid)
+{
+	PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state;
+	const char	   *p_values[1];
+	int				p_lengths[1];
+	AttrNumber		nattrs;
+	PGresult	   *res;
+	int				n_rows;
+
+	if (!fmstate->p_name)
+		prepare_foreign_modify(fmstate);
+
+	nattrs = setup_exec_prepared(resultRelInfo,
+								 rowid, NULL,
+								 p_values, p_lengths);
+	Assert(fmstate->p_nums == nattrs);
+
+	res = PQexecPrepared(fmstate->conn,
+						 fmstate->p_name,
+						 nattrs,
+						 p_values,
+						 p_lengths,
+						 NULL, 0);
+	if (!res ||  PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		PQclear(res);
+		elog(ERROR, "could not execute prepared statement (%s): %s",
+			 fmstate->query, PQerrorMessage(fmstate->conn));
+    }
+	n_rows = atoi(PQcmdTuples(res));
+    PQclear(res);
+
+	return (n_rows > 0 ? true : false);
+}
+
+static TupleTableSlot*
+postgresExecForeignUpdate(ResultRelInfo *resultRelInfo,
+						  const char *rowid, TupleTableSlot *slot)
+{
+	PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state;
+	const char	  **p_values  = alloca(sizeof(char *) * (fmstate->p_nums + 1));
+	int			   *p_lengths = alloca(sizeof(int) * (fmstate->p_nums + 1));
+	AttrNumber		nattrs;
+	PGresult	   *res;
+	int				n_rows;
+
+	if (!fmstate->p_name)
+		prepare_foreign_modify(fmstate);
+
+	nattrs = setup_exec_prepared(resultRelInfo,
+								 rowid, slot,
+								 p_values, p_lengths);
+	Assert(fmstate->p_nums == nattrs);
+
+	res = PQexecPrepared(fmstate->conn,
+						 fmstate->p_name,
+						 nattrs,
+						 p_values,
+						 p_lengths,
+						 NULL, 0);
+	if (!res || (!fmstate->has_returning ?
+				 PQresultStatus(res) != PGRES_COMMAND_OK :
+				 PQresultStatus(res) != PGRES_TUPLES_OK))
+	{
+		PQclear(res);
+		elog(ERROR, "could not execute prepared statement (%s): %s",
+			 fmstate->query, PQerrorMessage(fmstate->conn));
+	}
+	n_rows = atoi(PQcmdTuples(res));
+	if (n_rows > 0 && fmstate->has_returning)
+		store_returning_result(fmstate, slot, res);
+	PQclear(res);
+
+	return (n_rows > 0 ? slot : NULL);
+}
+
+static void
+postgresEndForeignModify(ResultRelInfo *resultRelInfo)
+{
+	PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state;
+
+	ReleaseConnection(fmstate->conn, false);
+	fmstate->conn = NULL;
+}
diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h
index b5cefb8..8225013 100644
--- a/contrib/postgres_fdw/postgres_fdw.h
+++ b/contrib/postgres_fdw/postgres_fdw.h
@@ -30,7 +30,8 @@ int GetFetchCountOption(ForeignTable *table, ForeignServer *server);
 void deparseSimpleSql(StringInfo buf,
 					  PlannerInfo *root,
 					  RelOptInfo *baserel,
-					  List *local_conds);
+					  List *local_conds,
+					  AttrNumber anum_rowid);
 void appendWhereClause(StringInfo buf,
 					   bool has_where,
 					   List *exprs,
@@ -41,5 +42,10 @@ void classifyConditions(PlannerInfo *root,
 						List **param_conds,
 						List **local_conds);
 void deparseAnalyzeSql(StringInfo buf, Relation rel);
+void deparseInsertSql(StringInfo buf, PlannerInfo *root, Index rtindex,
+					  List *targetAttrs, bool has_returning);
+void deparseUpdateSql(StringInfo buf, PlannerInfo *root, Index rtindex,
+					  List *targetAttrs, bool has_returning);
+void deparseDeleteSql(StringInfo buf, PlannerInfo *root, Index rtindex);
 
 #endif /* POSTGRESQL_FDW_H */
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 7845e70..89e6cf4 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -300,6 +300,37 @@ ERROR OUT;          -- ERROR
 SELECT srvname FROM postgres_fdw_connections;
 
 -- ===================================================================
+-- test for writable foreign table stuff (PoC stage now)
+-- ===================================================================
+EXPLAIN(verbose) 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) 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) 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;
+
+-- In case of remote table has before-row trigger or default with returning
+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 *;
+
+-- ===================================================================
 -- cleanup
 -- ===================================================================
 DROP OPERATOR === (int, int) CASCADE;
#49Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Kohei KaiGai (#48)
2 attachment(s)
Re: [v9.3] writable foreign tables

I noticed the v10 patch cannot be applied on the latest master branch
cleanly. The attached ones were rebased.

I also noticed that deparse_expression() assumes relation never has
columns with attribute number equal or larger than number of columns
of the relation, thus it makes a problem when EXPLAIN shows a plan
tree including Var node towards pseudo-column.
So, I adjusted the code to construct column-name array being used
for EXPLAIN output.

I don't touch the part-2 portion that extends Hanada-san's postgres_fdw
v5 patch.

Thanks,

2012/12/23 Kohei KaiGai <kaigai@kaigai.gr.jp>:

The attached patches are revised version for before-row triggers or
default setting.

I adjusted APIs to allow FDW drivers to modify the supplied slot
according to the row
being actually inserted / updated on the remote side.
Now, relevant handlers are declared as follows:

TupleTableSlot *
ExecForeignInsert (ResultRelInfo *resultRelInfo,
TupleTableSlot *slot);

TupleTableSlot *
ExecForeignUpdate (ResultRelInfo *resultRelInfo,
const char *rowid,
TupleTableSlot *slot);

bool
ExecForeignDelete (ResultRelInfo *resultRelInfo,
const char *rowid);

If nothing were modified, insert or update handler usually returns the supplied
slot as is. Elsewhere, it can return a modified image of tuple according to the
result of remote query execution, including default setting or
before-row trigger.

Let's see the following examples with enhanced postgres_fdw.

postgres=# CREATE FOREIGN TABLE ft1 (a int, b text, c date) SERVER
loopback OPTIONS(relname 't1');
CREATE FOREIGN TABLE
postgres=# SELECT * FROM ft1;
a | b | c
---+-----+------------
1 | aaa | 2012-12-10
2 | bbb | 2012-12-15
3 | ccc | 2012-12-20
(3 rows)

==> ft1 is a foreign table behalf of t1 on loopback server.

postgres=# ALTER TABLE t1 ALTER c SET DEFAULT current_date;
ALTER TABLE
postgres=# CREATE OR REPLACE FUNCTION t1_trig_func() RETURNS trigger AS $$
postgres$# BEGIN
postgres$# NEW.b = NEW.b || '_trig_update';
postgres$# RETURN NEW;
postgres$# END;
postgres$# $$ LANGUAGE plpgsql;
CREATE FUNCTION
postgres=# CREATE TRIGGER t1_br_trig BEFORE INSERT OR UPDATE ON t1 FOR
EACH ROW EXECUTE PROCEDURE t1_trig_func();
CREATE TRIGGER

==> The "t1" table has default setting and before-row triggers.

postgres=# INSERT INTO ft1 VALUES (4,'ddd','2012-12-05'),
(5,'eee',null) RETURNING *;
a | b | c
---+-----------------+------------
4 | ddd_trig_update | 2012-12-05
5 | eee_trig_update |
(2 rows)

INSERT 0 2
postgres=# INSERT INTO ft1 VALUES (6, 'fff') RETURNING *;
a | b | c
---+-----------------+------------
6 | fff_trig_update | 2012-12-23
(1 row)

INSERT 0 1

==> RETURNING clause can back modified image on the remote side.
Please note that the results are affected by default-setting and
before-row procedure on remote side.

postgres=# UPDATE ft1 SET a = a + 100 RETURNING *;
a | b | c
-----+-----------------------------+------------
101 | aaa_trig_update | 2012-12-10
102 | bbb_trig_update | 2012-12-15
103 | ccc_trig_update | 2012-12-20
104 | ddd_trig_update_trig_update | 2012-12-05
105 | eee_trig_update_trig_update |
106 | fff_trig_update_trig_update | 2012-12-23
(6 rows)

UPDATE 6

==> update command also

The modified API lost a feature to return the number of rows being affected
that might be useful in case when underlying query multiple rows on the
remote side. However, I rethought it does not really make sense.
If FDW driver support a feature to push down whole UPDATE or DELETE
command towards simple queries, it is not so difficult to cache the result
of the query that affect multiple rows, then the relevant routine can pop
the cached result for each invocation without remote query execution on
demand.

Thanks,

2012/12/18 Kohei KaiGai <kaigai@kaigai.gr.jp>:

Hi,

2012/12/18 Ronan Dunklau <rdunklau@gmail.com>:

Hello.

I've tried to implement this API for our Multicorn FDW, and I have a few
questions about the API.

First, I don't understand what are the requirements on the "rowid"
pseudo-column values.

In particular, this sentence from a previous mail makes it ambiguous to me:

At the beginning, I constructed the rowid pseudo-column using
INTERNALOID, but it made troubles when fetched tuples are
materialized prior to table modification.
So, the "rowid" argument of them are re-defined as "const char *".

Does that mean that one should only store a cstring in the rowid
pseudo-column ? Or can one store an arbitrary pointer ? Currently, I'm
building a text value using cstring_to_text_with_len. Could there be a
problem with that ?

All what we require on the rowid pseudo-column is it has capability to
identify a particular row on the remote side. In case of postgres_fdw,
ctid of the relevant table is sufficient for the purpose.
I don't recommend to set the rowid field an arbitrary pointer, because
scanned tuple may be materialized between scanning and modifying
foreign table, thus, the arbitrary pointer shall be dealt under the
assumption of cstring data type.

Secondly, how does one use a returning clause ?
I've tried to look at the postgres_fdw code, but it does not seem to handle
that.

For what its worth, knowing that the postgres_fdw is still in WIP status,
here is a couple result of my experimentation with it:

- Insert into a foreign table mapped to a table with a "before" trigger,
using a returning clause, will return the values as they were before being
modified by the trigger.
- Same thing, but if the trigger prevents insertion (returning NULL), the
"would-have-been" inserted row is still returned, although the insert
statement reports zero rows.

Hmm. Do you want to see the "real" final contents of the rows being inserted,
don't you. Yep, the proposed interface does not have capability to modify
the supplied tuple on ExecForeignInsert or other methods.

Probably, it needs to adjust interface to allow FDW drivers to return
a modified HeapTuple or TupleTableSlot for RETURNING calculation.
(As trigger doing, it can return the given one as-is if no change)

Let me investigate the code around this topic.

- Inserting into a table with default values does not work as intended,
since missing values are replaced by a null value in the remote statement.

It might be a bug of my proof-of-concept portion at postgres_fdw.
The prepared INSERT statement should list up columns being actually
used only, not all ones unconditionally.

Thanks,

What can be done to make the behaviour more consistent ?

I'm very excited about this feature, thank you for making this possible.

Regards,
--
Ronan Dunklau

2012/12/14 Albe Laurenz <laurenz.albe@wien.gv.at>

Kohei KaiGai wrote:

I came up with one more query that causes a problem:

[...]

This causes a deadlock, but one that is not detected;
the query just keeps hanging.

The UPDATE in the CTE has the rows locked, so the
SELECT ... FOR UPDATE issued via the FDW connection will hang
indefinitely.

I wonder if that's just a pathological corner case that we shouldn't
worry about. Loopback connections for FDWs themselves might not
be so rare, for example as a substitute for autonomous subtransactions.

I guess it is not easily possible to detect such a situation or
to do something reasonable about it.

It is not avoidable problem due to the nature of distributed database
system,
not only loopback scenario.

In my personal opinion, I'd like to wait for someone implements
distributed
lock/transaction manager on top of the background worker framework being
recently committed, to intermediate lock request.
However, it will take massive amount of efforts to existing
lock/transaction
management layer, not only enhancement of FDW APIs. It is obviously out
of scope in this patch.

So, I'd like to suggest authors of FDW that support writable features to
put
mention about possible deadlock scenario in their documentation.
At least, above writable CTE example is a situation that two different
sessions
concurrently update the "test" relation, thus, one shall be blocked.

Fair enough.

I tried to overhaul the documentation, see the attached patch.

There was one thing that I was not certain of:
You say that for writable foreign tables, BeginForeignModify
and EndForeignModify *must* be implemented.
I thought that these were optional, and if you can do your work

with just, say, ExecForeignDelete, you could do that.

Yes, that's right. What I wrote was incorrect.
If FDW driver does not require any state during modification of
foreign tables, indeed, these are not mandatory handler.

I have updated the documentation, that was all I changed in the
attached patches.

OK. I split the patch into two portion, part-1 is the APIs relevant
patch, part-2 is relevant to postgres_fdw patch.

Great.

I'll mark the patch as "ready for committer".

Yours,
Laurenz Albe

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

--
KaiGai Kohei <kaigai@kaigai.gr.jp>

--
KaiGai Kohei <kaigai@kaigai.gr.jp>

--
KaiGai Kohei <kaigai@kaigai.gr.jp>

Attachments:

pgsql-v9.3-writable-fdw-poc.v11.part-2.patchapplication/octet-stream; name=pgsql-v9.3-writable-fdw-poc.v11.part-2.patchDownload
 contrib/postgres_fdw/connection.c              |   70 +-
 contrib/postgres_fdw/connection.h              |    8 +-
 contrib/postgres_fdw/deparse.c                 |  158 ++-
 contrib/postgres_fdw/expected/postgres_fdw.out | 1232 +++++++++++++++++++++++-
 contrib/postgres_fdw/postgres_fdw.c            |  504 +++++++++-
 contrib/postgres_fdw/postgres_fdw.h            |    8 +-
 contrib/postgres_fdw/sql/postgres_fdw.sql      |   31 +
 7 files changed, 1978 insertions(+), 33 deletions(-)

diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index eab8b87..9ca7fbf 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -43,7 +43,7 @@ typedef struct ConnCacheEntry
 	Oid				serverid;	/* oid of foreign server */
 	Oid				userid;		/* oid of local user */
 
-	bool			use_tx;		/* true when using remote transaction */
+	int				conntx;		/* one of PGSQL_FDW_CONNTX_* */
 	int				refs;		/* reference counter */
 	PGconn		   *conn;		/* foreign server connection */
 } ConnCacheEntry;
@@ -65,6 +65,8 @@ cleanup_connection(ResourceReleasePhase phase,
 static PGconn *connect_pg_server(ForeignServer *server, UserMapping *user);
 static void begin_remote_tx(PGconn *conn);
 static void abort_remote_tx(PGconn *conn);
+static void commit_remote_tx(PGconn *conn);
+static void deallocate_remote_prepare(PGconn *conn);
 
 /*
  * Get a PGconn which can be used to execute foreign query on the remote
@@ -80,7 +82,7 @@ static void abort_remote_tx(PGconn *conn);
  * FDW object to invalidate already established connections.
  */
 PGconn *
-GetConnection(ForeignServer *server, UserMapping *user, bool use_tx)
+GetConnection(ForeignServer *server, UserMapping *user, int conntx)
 {
 	bool			found;
 	ConnCacheEntry *entry;
@@ -126,7 +128,7 @@ GetConnection(ForeignServer *server, UserMapping *user, bool use_tx)
 	entry = hash_search(ConnectionHash, &key, HASH_ENTER, &found);
 	if (!found)
 	{
-		entry->use_tx = false;
+		entry->conntx = PGSQL_FDW_CONNTX_NONE;
 		entry->refs = 0;
 		entry->conn = NULL;
 	}
@@ -162,7 +164,7 @@ GetConnection(ForeignServer *server, UserMapping *user, bool use_tx)
 		{
 			/* Clear connection cache entry on error case. */
 			PQfinish(entry->conn);
-			entry->use_tx = false;
+			entry->conntx = PGSQL_FDW_CONNTX_NONE;
 			entry->refs = 0;
 			entry->conn = NULL;
 			PG_RE_THROW();
@@ -182,10 +184,11 @@ GetConnection(ForeignServer *server, UserMapping *user, bool use_tx)
 	 * are in.  We need to remember whether this connection uses remote
 	 * transaction to abort it when this connection is released completely.
 	 */
-	if (use_tx && !entry->use_tx)
+	if (conntx > entry->conntx)
 	{
-		begin_remote_tx(entry->conn);
-		entry->use_tx = use_tx;
+		if (entry->conntx == PGSQL_FDW_CONNTX_NONE)
+			begin_remote_tx(entry->conn);
+		entry->conntx = conntx;
 	}
 
 	return entry->conn;
@@ -355,12 +358,45 @@ abort_remote_tx(PGconn *conn)
 	PQclear(res);
 }
 
+static void
+commit_remote_tx(PGconn *conn)
+{
+	PGresult	   *res;
+
+	elog(DEBUG3, "committing remote transaction");
+
+	res = PQexec(conn, "COMMIT TRANSACTION");
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		PQclear(res);
+		elog(ERROR, "could not commit transaction: %s", PQerrorMessage(conn));
+	}
+	PQclear(res);
+}
+
+static void
+deallocate_remote_prepare(PGconn *conn)
+{
+	PGresult	   *res;
+
+	elog(DEBUG3, "deallocating remote prepares");
+
+	res = PQexec(conn, "DEALLOCATE PREPARE ALL");
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		PQclear(res);
+		elog(ERROR, "could not deallocate prepared statement: %s",
+			 PQerrorMessage(conn));
+	}
+	PQclear(res);
+}
+
 /*
  * Mark the connection as "unused", and close it if the caller was the last
  * user of the connection.
  */
 void
-ReleaseConnection(PGconn *conn)
+ReleaseConnection(PGconn *conn, bool is_abort)
 {
 	HASH_SEQ_STATUS		scan;
 	ConnCacheEntry	   *entry;
@@ -412,7 +448,7 @@ ReleaseConnection(PGconn *conn)
 			 PQtransactionStatus(conn) == PQTRANS_INERROR ? "INERROR" :
 			 "UNKNOWN");
 		PQfinish(conn);
-		entry->use_tx = false;
+		entry->conntx = PGSQL_FDW_CONNTX_NONE;
 		entry->refs = 0;
 		entry->conn = NULL;
 		return;
@@ -430,10 +466,16 @@ ReleaseConnection(PGconn *conn)
 	 * If this connection uses remote transaction and there is no user other
 	 * than the caller, abort the remote transaction and forget about it.
 	 */
-	if (entry->use_tx && entry->refs == 0)
+	if (entry->conntx > PGSQL_FDW_CONNTX_NONE && entry->refs == 0)
 	{
-		abort_remote_tx(conn);
-		entry->use_tx = false;
+		if (entry->conntx > PGSQL_FDW_CONNTX_READ_ONLY)
+			deallocate_remote_prepare(conn);
+		if (is_abort || entry->conntx == PGSQL_FDW_CONNTX_READ_ONLY)
+			abort_remote_tx(conn);
+		else
+			commit_remote_tx(conn);
+
+		entry->conntx = PGSQL_FDW_CONNTX_NONE;
 	}
 }
 
@@ -485,7 +527,7 @@ cleanup_connection(ResourceReleasePhase phase,
 		elog(DEBUG3, "discard postgres_fdw connection %p due to resowner cleanup",
 			 entry->conn);
 		PQfinish(entry->conn);
-		entry->use_tx = false;
+		entry->conntx = PGSQL_FDW_CONNTX_NONE;
 		entry->refs = 0;
 		entry->conn = NULL;
 	}
@@ -597,7 +639,7 @@ postgres_fdw_disconnect(PG_FUNCTION_ARGS)
 
 	/* Discard cached connection, and clear reference counter. */
 	PQfinish(entry->conn);
-	entry->use_tx = false;
+	entry->conntx = PGSQL_FDW_CONNTX_NONE;
 	entry->refs = 0;
 	entry->conn = NULL;
 
diff --git a/contrib/postgres_fdw/connection.h b/contrib/postgres_fdw/connection.h
index 4c9d850..f97cc8a 100644
--- a/contrib/postgres_fdw/connection.h
+++ b/contrib/postgres_fdw/connection.h
@@ -16,10 +16,14 @@
 #include "foreign/foreign.h"
 #include "libpq-fe.h"
 
+#define PGSQL_FDW_CONNTX_NONE			0
+#define PGSQL_FDW_CONNTX_READ_ONLY		1
+#define PGSQL_FDW_CONNTX_READ_WRITE		2
+
 /*
  * Connection management
  */
-PGconn *GetConnection(ForeignServer *server, UserMapping *user, bool use_tx);
-void ReleaseConnection(PGconn *conn);
+PGconn *GetConnection(ForeignServer *server, UserMapping *user, int conntx);
+void ReleaseConnection(PGconn *conn, bool is_abort);
 
 #endif /* CONNECTION_H */
diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
index 69e6a3e..ce8c2e7 100644
--- a/contrib/postgres_fdw/deparse.c
+++ b/contrib/postgres_fdw/deparse.c
@@ -12,6 +12,7 @@
  */
 #include "postgres.h"
 
+#include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/transam.h"
 #include "catalog/pg_class.h"
@@ -86,9 +87,11 @@ void
 deparseSimpleSql(StringInfo buf,
 				 PlannerInfo *root,
 				 RelOptInfo *baserel,
-				 List *local_conds)
+				 List *local_conds,
+				 AttrNumber anum_rowid)
 {
 	RangeTblEntry *rte;
+	Relation	rel;
 	ListCell   *lc;
 	StringInfoData	foreign_relname;
 	bool		first;
@@ -125,6 +128,24 @@ deparseSimpleSql(StringInfo buf,
 	}
 
 	/*
+	 * XXX - When this foreign table is target relation and RETURNING
+	 * clause reference some column, we have to mark these columns as
+	 * in-use. It is needed to support DELETE command, because INSERT
+	 * and UPDATE implicitly add references to all the regular columns
+	 * on baserel->reltargetlist.
+	 */
+	if (root->parse->resultRelation == baserel->relid &&
+		root->parse->returningList)
+	{
+		List   *attrs;
+
+		attrs = pull_var_clause((Node *) root->parse->returningList,
+								PVC_RECURSE_AGGREGATES,
+                                PVC_RECURSE_PLACEHOLDERS);
+		attr_used = list_union(attr_used, attrs);
+	}
+
+	/*
 	 * deparse SELECT clause
 	 *
 	 * List attributes which are in either target list or local restriction.
@@ -136,9 +157,10 @@ deparseSimpleSql(StringInfo buf,
 	 */
 	appendStringInfo(buf, "SELECT ");
 	rte = root->simple_rte_array[baserel->relid];
+	rel = heap_open(rte->relid, NoLock);
 	attr_used = list_union(attr_used, baserel->reltargetlist);
 	first = true;
-	for (attr = 1; attr <= baserel->max_attr; attr++)
+	for (attr = 1; attr <= RelationGetNumberOfAttributes(rel); attr++)
 	{
 		Var		   *var = NULL;
 		ListCell   *lc;
@@ -167,6 +189,10 @@ deparseSimpleSql(StringInfo buf,
 		else
 			appendStringInfo(buf, "NULL");
 	}
+	if (anum_rowid != InvalidAttrNumber)
+		appendStringInfo(buf, "%sctid", (first ? "" : ", "));
+
+	heap_close(rel, NoLock);
 	appendStringInfoChar(buf, ' ');
 
 	/*
@@ -283,6 +309,134 @@ deparseAnalyzeSql(StringInfo buf, Relation rel)
 }
 
 /*
+ * deparse RETURNING clause of INSERT/UPDATE/DELETE
+ */
+static void
+deparseReturningSql(StringInfo buf, PlannerInfo *root, Index rtindex,
+					Relation frel)
+{
+	AttrNumber	i, nattrs = RelationGetNumberOfAttributes(frel);
+
+	appendStringInfo(buf, " RETURNING ");
+	for (i=0; i < nattrs; i++)
+	{
+		Form_pg_attribute attr = RelationGetDescr(frel)->attrs[i];
+
+		if (i > 0)
+			appendStringInfo(buf, ",");
+
+		if (attr->attisdropped)
+			appendStringInfo(buf, "null");
+		else
+		{
+			Var		var;
+
+			var.varno = rtindex;
+			var.varattno = attr->attnum;
+			deparseVar(buf, &var, root);
+		}
+	}
+}
+
+/*
+ * deparse remote INSERT statement
+ */
+void
+deparseInsertSql(StringInfo buf, PlannerInfo *root, Index rtindex,
+				 List *targetAttrs, bool has_returning)
+{
+	RangeTblEntry  *rte = root->simple_rte_array[rtindex];
+	Relation		frel = heap_open(rte->relid, NoLock);
+	ListCell	   *lc;
+	AttrNumber		pindex = 1;
+
+	appendStringInfo(buf, "INSERT INTO ");
+	deparseRelation(buf, rte);
+	appendStringInfo(buf, "(");
+
+	foreach (lc, targetAttrs)
+	{
+		Var		var;
+		Form_pg_attribute	attr
+			= RelationGetDescr(frel)->attrs[lfirst_int(lc) - 1];
+
+		Assert(!attr->attisdropped);
+		if (lc != list_head(targetAttrs))
+			appendStringInfo(buf, ",");
+
+		var.varno = rtindex;
+		var.varattno = attr->attnum;
+		deparseVar(buf, &var, root);
+	}
+	appendStringInfo(buf, ") VALUES (");
+
+	foreach (lc, targetAttrs)
+	{
+		appendStringInfo(buf, "%s$%d", (pindex == 1 ? "" : ","), pindex);
+		pindex++;
+	}
+	appendStringInfo(buf, ")");
+
+	if (has_returning)
+		deparseReturningSql(buf, root, rtindex, frel);
+
+	heap_close(frel, NoLock);
+}
+
+/*
+ * deparse remote UPDATE statement
+ */
+void
+deparseUpdateSql(StringInfo buf, PlannerInfo *root, Index rtindex,
+				 List *targetAttrs, bool has_returning)
+{
+	RangeTblEntry  *rte = root->simple_rte_array[rtindex];
+	Relation		frel = heap_open(rte->relid, NoLock);
+	ListCell	   *lc;
+	AttrNumber		pindex = 2;
+
+	appendStringInfo(buf, "UPDATE ");
+	deparseRelation(buf, rte);
+	appendStringInfo(buf, " SET ");
+
+	foreach (lc, targetAttrs)
+	{
+		Var		var;
+		Form_pg_attribute	attr
+			= RelationGetDescr(frel)->attrs[lfirst_int(lc) - 1];
+
+		Assert(!attr->attisdropped);
+
+		if (lc != list_head(targetAttrs))
+			appendStringInfo(buf, ",");
+
+		var.varno = rtindex;
+		var.varattno = attr->attnum;
+		deparseVar(buf, &var, root);
+		appendStringInfo(buf, "=$%d", pindex++);
+	}
+	appendStringInfo(buf, " WHERE ctid=$1");
+
+	if (has_returning)
+		deparseReturningSql(buf, root, rtindex, frel);
+
+	heap_close(frel, NoLock);
+}
+
+/*
+ * deparse remote DELETE statement
+ */
+void
+deparseDeleteSql(StringInfo buf, PlannerInfo *root, Index rtindex)
+{
+	RangeTblEntry  *rte = root->simple_rte_array[rtindex];
+
+	appendStringInfo(buf, "DELETE FROM ");
+	deparseRelation(buf, rte);
+	appendStringInfo(buf, " WHERE ctid = $1");
+}
+
+/*
  * Deparse given expression into buf.  Actual string operation is delegated to
  * node-type-specific functions.
  *
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index f81c727..3a5d4d8 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -736,15 +736,1245 @@ SELECT srvname FROM postgres_fdw_connections;
 (0 rows)
 
 -- ===================================================================
+-- test for writable foreign table stuff (PoC stage now)
+-- ===================================================================
+EXPLAIN(verbose) INSERT INTO ft2 (c1,c2,c3) (SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20);
+                                                                                                            QUERY PLAN                                                                                                             
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Insert on public.ft2  (cost=100.00..100.03 rows=1 width=40)
+   ->  Subquery Scan on "*SELECT*"  (cost=100.00..100.03 rows=1 width=40)
+         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  (cost=100.00..100.02 rows=1 width=40)
+               Output: ((ft2_1.c1 + 1000)), ((ft2_1.c2 + 100)), ((ft2_1.c3 || ft2_1.c3))
+               ->  Foreign Scan on public.ft2 ft2_1  (cost=100.00..100.02 rows=1 width=40)
+                     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"
+(8 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) 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  (cost=200.00..200.04 rows=1 width=344)
+   ->  Nested Loop  (cost=200.00..200.04 rows=1 width=344)
+         Output: NULL::integer, ft2.c1, (ft2.c2 + 500), (ft2.c3 || '_update9'::text), ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid, ft2.*, ft1.*
+         Join Filter: (ft2.c2 = ft1.c1)
+         ->  Foreign Scan on public.ft2  (cost=100.00..100.01 rows=1 width=232)
+               Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid, ft2.*
+               Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8,ctid FROM "S 1"."T 1" FOR UPDATE
+         ->  Foreign Scan on public.ft1  (cost=100.00..100.01 rows=1 width=116)
+               Output: ft1.*, ft1.c1
+               Remote SQL: SELECT "C 1", NULL, NULL, NULL, NULL, NULL, NULL, NULL FROM "S 1"."T 1" WHERE ((("C 1" OPERATOR(pg_catalog.%) 10) OPERATOR(pg_catalog.=) 9))
+(10 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) DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2;
+                                                                               QUERY PLAN                                                                               
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Delete on public.ft2  (cost=200.00..200.03 rows=1 width=256)
+   ->  Nested Loop  (cost=200.00..200.03 rows=1 width=256)
+         Output: ft2.ctid, ft2.*, ft1.*
+         Join Filter: (ft2.c2 = ft1.c1)
+         ->  Foreign Scan on public.ft2  (cost=100.00..100.01 rows=1 width=148)
+               Output: ft2.ctid, ft2.*, ft2.c2
+               Remote SQL: SELECT NULL, c2, NULL, NULL, NULL, NULL, NULL, NULL,ctid FROM "S 1"."T 1" FOR UPDATE
+         ->  Foreign Scan on public.ft1  (cost=100.00..100.01 rows=1 width=116)
+               Output: ft1.*, ft1.c1
+               Remote SQL: SELECT "C 1", NULL, NULL, NULL, NULL, NULL, NULL, NULL FROM "S 1"."T 1" WHERE ((("C 1" OPERATOR(pg_catalog.%) 10) OPERATOR(pg_catalog.=) 2))
+(10 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)
+
+-- In case of remote table has before-row trigger or default with returning
+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)
+
+-- ===================================================================
 -- cleanup
 -- ===================================================================
 DROP OPERATOR === (int, int) CASCADE;
 DROP OPERATOR !== (int, int) CASCADE;
 DROP FUNCTION postgres_fdw_abs(int);
 DROP SCHEMA "S 1" CASCADE;
-NOTICE:  drop cascades to 2 other objects
+NOTICE:  drop cascades to 3 other objects
 DETAIL:  drop cascades to table "S 1"."T 1"
 drop cascades to table "S 1"."T 2"
+drop cascades to function "S 1".f_brtrig()
 DROP TYPE user_enum CASCADE;
 NOTICE:  drop cascades to 2 other objects
 DETAIL:  drop cascades to foreign table ft2 column c8
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 6b870ab..420bb4d 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -14,6 +14,7 @@
 #include "fmgr.h"
 
 #include "access/htup_details.h"
+#include "access/sysattr.h"
 #include "catalog/pg_foreign_server.h"
 #include "catalog/pg_foreign_table.h"
 #include "catalog/pg_type.h"
@@ -27,6 +28,7 @@
 #include "optimizer/pathnode.h"
 #include "optimizer/planmain.h"
 #include "optimizer/restrictinfo.h"
+#include "parser/parsetree.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -59,6 +61,7 @@ typedef struct PostgresFdwPlanState {
 	List		   *param_conds;
 	List		   *local_conds;
 	int				width;			/* obtained by remote EXPLAIN */
+	AttrNumber		anum_rowid;
 
 	/* Cached catalog information. */
 	ForeignTable   *table;
@@ -150,6 +153,24 @@ typedef struct PostgresAnalyzeState
 } PostgresAnalyzeState;
 
 /*
+ * Describes a state of modify request for a foreign table
+ */
+typedef struct PostgresFdwModifyState
+{
+	PGconn	   *conn;
+	char	   *query;
+	bool		has_returning;
+	List	   *target_attrs;
+	char	   *p_name;
+	int			p_nums;
+	Oid		   *p_types;
+	FmgrInfo   *p_flinfo;
+	Oid		   *r_ioparam;
+	FmgrInfo   *r_flinfo;
+	MemoryContext	es_query_cxt;
+} PostgresFdwModifyState;
+
+/*
  * SQL functions
  */
 extern Datum postgres_fdw_handler(PG_FUNCTION_ARGS);
@@ -158,6 +179,11 @@ PG_FUNCTION_INFO_V1(postgres_fdw_handler);
 /*
  * FDW callback routines
  */
+static AttrNumber postgresGetForeignRelWidth(PlannerInfo *root,
+											 RelOptInfo *baserel,
+											 Relation foreignrel,
+											 bool inhparent,
+											 List *targetList);
 static void postgresGetForeignRelSize(PlannerInfo *root,
 									  RelOptInfo *baserel,
 									  Oid foreigntableid);
@@ -179,6 +205,23 @@ static void postgresEndForeignScan(ForeignScanState *node);
 static bool postgresAnalyzeForeignTable(Relation relation,
 										AcquireSampleRowsFunc *func,
 										BlockNumber *totalpages);
+static List *postgresPlanForeignModify(PlannerInfo *root,
+									   ModifyTable *plan,
+									   Index resultRelation,
+									   Plan *subplan);
+static void postgresBeginForeignModify(ModifyTableState *mtstate,
+									   ResultRelInfo *resultRelInfo,
+									   List *fdw_private,
+									   Plan *subplan,
+									   int eflags);
+static TupleTableSlot *postgresExecForeignInsert(ResultRelInfo *rinfo,
+												 TupleTableSlot *slot);
+static bool postgresExecForeignDelete(ResultRelInfo *rinfo,
+									  const char *rowid);
+static TupleTableSlot * postgresExecForeignUpdate(ResultRelInfo *rinfo,
+												  const char *rowid,
+												  TupleTableSlot *slot);
+static void postgresEndForeignModify(ResultRelInfo *rinfo);
 
 /*
  * Helper functions
@@ -231,6 +274,7 @@ postgres_fdw_handler(PG_FUNCTION_ARGS)
 	FdwRoutine	*routine = makeNode(FdwRoutine);
 
 	/* Required handler functions. */
+	routine->GetForeignRelWidth = postgresGetForeignRelWidth;
 	routine->GetForeignRelSize = postgresGetForeignRelSize;
 	routine->GetForeignPaths = postgresGetForeignPaths;
 	routine->GetForeignPlan = postgresGetForeignPlan;
@@ -239,6 +283,12 @@ postgres_fdw_handler(PG_FUNCTION_ARGS)
 	routine->IterateForeignScan = postgresIterateForeignScan;
 	routine->ReScanForeignScan = postgresReScanForeignScan;
 	routine->EndForeignScan = postgresEndForeignScan;
+	routine->PlanForeignModify = postgresPlanForeignModify;
+	routine->BeginForeignModify = postgresBeginForeignModify;
+	routine->ExecForeignInsert = postgresExecForeignInsert;
+	routine->ExecForeignDelete = postgresExecForeignDelete;
+	routine->ExecForeignUpdate = postgresExecForeignUpdate;
+	routine->EndForeignModify = postgresEndForeignModify;
 
 	/* Optional handler functions. */
 	routine->AnalyzeForeignTable = postgresAnalyzeForeignTable;
@@ -247,6 +297,34 @@ postgres_fdw_handler(PG_FUNCTION_ARGS)
 }
 
 /*
+ * postgresGetForeignRelWidth
+ *		Informs how many columns (including pseudo ones) are needed.
+ */
+static AttrNumber
+postgresGetForeignRelWidth(PlannerInfo *root,
+						   RelOptInfo *baserel,
+						   Relation foreignrel,
+						   bool inhparent,
+						   List *targetList)
+{
+	PostgresFdwPlanState *fpstate = palloc0(sizeof(PostgresFdwPlanState));
+
+	baserel->fdw_private = fpstate;
+
+	/* does rowid pseudo-column is required? */
+	fpstate->anum_rowid = get_pseudo_rowid_column(baserel, targetList);
+	if (fpstate->anum_rowid != InvalidAttrNumber)
+	{
+		RangeTblEntry *rte = rt_fetch(baserel->relid,
+									  root->parse->rtable);
+		rte->eref->colnames = lappend(rte->eref->colnames,
+									  makeString("ctid"));
+		return fpstate->anum_rowid;
+	}
+	return RelationGetNumberOfAttributes(foreignrel);
+}
+
+/*
  * postgresGetForeignRelSize
  *		Estimate # of rows and width of the result of the scan
  *
@@ -283,7 +361,7 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	 * We use PostgresFdwPlanState to pass various information to subsequent
 	 * functions.
 	 */
-	fpstate = palloc0(sizeof(PostgresFdwPlanState));
+	fpstate = baserel->fdw_private;
 	initStringInfo(&fpstate->sql);
 	sql = &fpstate->sql;
 
@@ -320,10 +398,9 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	 */
 	classifyConditions(root, baserel, &remote_conds, &param_conds,
 					   &local_conds);
-	deparseSimpleSql(sql, root, baserel, local_conds);
+	deparseSimpleSql(sql, root, baserel, local_conds, fpstate->anum_rowid);
 	if (list_length(remote_conds) > 0)
 		appendWhereClause(sql, true, remote_conds, root);
-	elog(DEBUG3, "Query SQL: %s", sql->data);
 
 	/*
 	 * If the table or the server is configured to use remote EXPLAIN, connect
@@ -337,10 +414,10 @@ postgresGetForeignRelSize(PlannerInfo *root,
 		PGconn		   *conn;
 
 		user = GetUserMapping(GetOuterUserId(), server->serverid);
-		conn = GetConnection(server, user, false);
+		conn = GetConnection(server, user, PGSQL_FDW_CONNTX_NONE);
 		get_remote_estimate(sql->data, conn, &rows, &width,
 							&startup_cost, &total_cost);
-		ReleaseConnection(conn);
+		ReleaseConnection(conn, false);
 
 		/*
 		 * Estimate selectivity of conditions which are not used in remote
@@ -391,7 +468,6 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	fpstate->width = width;
 	fpstate->table = table;
 	fpstate->server = server;
-	baserel->fdw_private = (void *) fpstate;
 }
 
 /*
@@ -592,7 +668,7 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 	table = GetForeignTable(relid);
 	server = GetForeignServer(table->serverid);
 	user = GetUserMapping(GetOuterUserId(), server->serverid);
-	conn = GetConnection(server, user, true);
+	conn = GetConnection(server, user, PGSQL_FDW_CONNTX_READ_ONLY);
 	festate->conn = conn;
 
 	/* Result will be filled in first Iterate call. */
@@ -724,7 +800,7 @@ postgresEndForeignScan(ForeignScanState *node)
 	 * end of the scan to make the lifespan of remote transaction same as the
 	 * local query.
 	 */
-	ReleaseConnection(festate->conn);
+	ReleaseConnection(festate->conn, false);
 	festate->conn = NULL;
 
 	/* Discard fetch results */
@@ -790,7 +866,7 @@ get_remote_estimate(const char *sql, PGconn *conn,
 		PQclear(res);
 
 		/* Release connection and let connection manager cleanup. */
-		ReleaseConnection(conn);
+		ReleaseConnection(conn, true);
 
 		PG_RE_THROW();
 	}
@@ -947,7 +1023,7 @@ execute_query(ForeignScanState *node)
 		PQclear(res);
 
 		/* Release connection and let connection manager cleanup. */
-		ReleaseConnection(conn);
+		ReleaseConnection(conn, true);
 
 		/* propagate error */
 		PG_RE_THROW();
@@ -1105,6 +1181,8 @@ postgres_fdw_error_callback(void *arg)
 
 	relname = get_rel_name(errpos->relid);
 	colname = get_attname(errpos->relid, errpos->cur_attno);
+	if (!colname)
+		colname = "pseudo-column";
 	errcontext("column %s of foreign table %s",
 			   quote_identifier(colname), quote_identifier(relname));
 }
@@ -1172,7 +1250,7 @@ postgresAcquireSampleRowsFunc(Relation relation, int elevel,
 	table = GetForeignTable(relation->rd_id);
 	server = GetForeignServer(table->serverid);
 	user = GetUserMapping(GetOuterUserId(), server->serverid);
-	conn = GetConnection(server, user, true);
+	conn = GetConnection(server, user, PGSQL_FDW_CONNTX_READ_ONLY);
 
 	/*
 	 * Acquire sample rows from the result set.
@@ -1239,13 +1317,13 @@ postgresAcquireSampleRowsFunc(Relation relation, int elevel,
 		PQclear(res);
 
 		/* Release connection and let connection manager cleanup. */
-		ReleaseConnection(conn);
+		ReleaseConnection(conn, true);
 
 		PG_RE_THROW();
 	}
 	PG_END_TRY();
 
-	ReleaseConnection(conn);
+	ReleaseConnection(conn, false);
 
 	/* We assume that we have no dead tuple. */
 	*totaldeadrows = 0.0;
@@ -1429,3 +1507,403 @@ analyze_row_processor(PGresult *res, PostgresAnalyzeState *astate, bool first)
 
 	return;
 }
+
+static List *
+postgresPlanForeignModify(PlannerInfo *root,
+						  ModifyTable *plan,
+						  Index resultRelation,
+						  Plan *subplan)
+{
+	CmdType			operation = plan->operation;
+	StringInfoData	sql;
+	List		   *targetAttrs = NIL;
+	bool			has_returning = (!!plan->returningLists);
+
+	initStringInfo(&sql);
+
+	/*
+	 * XXX - In case of UPDATE or DELETE commands are quite "simple",
+	 * we will be able to execute raw UPDATE or DELETE statement at
+	 * the stage of scan, instead of combination SELECT ... FOR UPDATE
+	 * and either of UPDATE or DELETE commands.
+	 * It should be an idea of optimization in the future version.
+	 *
+	 * XXX - FOR UPDATE should be appended on the remote query of scan
+	 * stage to avoid unexpected concurrent update on the target rows.
+	 */
+	if (operation == CMD_UPDATE || operation == CMD_DELETE)
+	{
+		ForeignScan	   *fscan;
+		Value		   *select_sql;
+
+		fscan = lookup_foreign_scan_plan(subplan, resultRelation);
+		if (!fscan)
+			elog(ERROR, "no underlying scan plan found in subplan tree");
+
+		select_sql = list_nth(fscan->fdw_private,
+							  FdwPrivateSelectSql);
+		appendStringInfo(&sql, "%s FOR UPDATE", strVal(select_sql));
+		strVal(select_sql) = pstrdup(sql.data);
+
+		resetStringInfo(&sql);
+	}
+
+	/*
+	 * XXX - In case of INSERT or UPDATE commands, it needs to list up
+	 * columns to be updated or inserted for performance optimization
+	 * and consistent behavior when DEFAULT is set on the remote table.
+	 */
+	if (operation == CMD_INSERT || operation == CMD_UPDATE)
+	{
+		RangeTblEntry  *rte = rt_fetch(resultRelation, root->parse->rtable);
+		Bitmapset	   *tmpset = bms_copy(rte->modifiedCols);
+		AttrNumber		col;
+
+		while ((col = bms_first_member(tmpset)) >= 0)
+		{
+			col += FirstLowInvalidHeapAttributeNumber;
+			if (col <= InvalidAttrNumber)
+				elog(ERROR, "system-column update is not supported");
+			targetAttrs = lappend_int(targetAttrs, col);
+		}
+	}
+
+	switch (operation)
+	{
+		case CMD_INSERT:
+			deparseInsertSql(&sql, root, resultRelation,
+							 targetAttrs, has_returning);
+			elog(DEBUG3, "Remote INSERT query: %s", sql.data);
+			break;
+		case CMD_UPDATE:
+			deparseUpdateSql(&sql, root, resultRelation,
+							 targetAttrs, has_returning);
+			elog(DEBUG3, "Remote UPDATE query: %s", sql.data);
+			break;
+		case CMD_DELETE:
+			deparseDeleteSql(&sql, root, resultRelation);
+			elog(DEBUG3, "Remote DELETE query: %s", sql.data);
+			break;
+		default:
+			elog(ERROR, "unexpected operation: %d", (int) operation);
+	}
+	return list_make3(makeString(sql.data),
+					  makeInteger(has_returning),
+					  targetAttrs);
+}
+
+static void
+postgresBeginForeignModify(ModifyTableState *mtstate,
+						   ResultRelInfo *resultRelInfo,
+						   List *fdw_private,
+						   Plan *subplan,
+						   int eflags)
+{
+	PostgresFdwModifyState *fmstate;
+	CmdType			operation = mtstate->operation;
+	Relation		frel = resultRelInfo->ri_RelationDesc;
+	AttrNumber		n_params;
+	ListCell	   *lc;
+	ForeignTable   *ftable;
+	ForeignServer  *fserver;
+	UserMapping	   *fuser;
+	Oid				typefnoid;
+	bool			isvarlena;
+
+	/*
+	 * Construct PostgresFdwExecutionState
+	 */
+	fmstate = palloc0(sizeof(PostgresFdwExecutionState));
+
+	ftable = GetForeignTable(RelationGetRelid(frel));
+	fserver = GetForeignServer(ftable->serverid);
+	fuser = GetUserMapping(GetOuterUserId(), fserver->serverid);
+
+	fmstate->query = strVal(linitial(fdw_private));
+	fmstate->has_returning = intVal(lsecond(fdw_private));
+	fmstate->target_attrs = lthird(fdw_private);
+	fmstate->conn = GetConnection(fserver, fuser,
+								  PGSQL_FDW_CONNTX_READ_WRITE);
+	n_params = list_length(fmstate->target_attrs) + 1;
+	fmstate->p_name = NULL;
+	fmstate->p_types = palloc0(sizeof(Oid) * n_params);
+	fmstate->p_flinfo = palloc0(sizeof(FmgrInfo) * n_params);
+
+	/* 1st parameter should be ctid on UPDATE or DELETE */
+	if (operation == CMD_UPDATE || operation == CMD_DELETE)
+	{
+		fmstate->p_types[fmstate->p_nums] = TIDOID;
+		getTypeOutputInfo(TIDOID, &typefnoid, &isvarlena);
+		fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
+		fmstate->p_nums++;
+	}
+	/* following parameters should be regular columns */
+	if (operation == CMD_UPDATE || operation == CMD_INSERT)
+	{
+		foreach (lc, fmstate->target_attrs)
+		{
+			Form_pg_attribute attr
+				= RelationGetDescr(frel)->attrs[lfirst_int(lc) - 1];
+
+			Assert(!attr->attisdropped);
+
+			fmstate->p_types[fmstate->p_nums] = attr->atttypid;
+			getTypeOutputInfo(attr->atttypid, &typefnoid, &isvarlena);
+			fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
+			fmstate->p_nums++;
+		}
+	}
+	Assert(fmstate->p_nums <= n_params);
+
+	/* input handlers for returning clause */
+	if (fmstate->has_returning)
+	{
+		AttrNumber	i, nattrs = RelationGetNumberOfAttributes(frel);
+
+		fmstate->r_ioparam = palloc0(sizeof(Oid) * nattrs);
+		fmstate->r_flinfo = palloc0(sizeof(FmgrInfo) * nattrs);
+		for (i=0; i < nattrs; i++)
+		{
+			Form_pg_attribute attr = RelationGetDescr(frel)->attrs[i];
+
+			if (attr->attisdropped)
+				continue;
+
+			getTypeInputInfo(attr->atttypid, &typefnoid,
+							 &fmstate->r_ioparam[i]);
+			fmgr_info(typefnoid, &fmstate->r_flinfo[i]);
+		}
+	}
+	fmstate->es_query_cxt = mtstate->ps.state->es_query_cxt;
+	resultRelInfo->ri_fdw_state = fmstate;
+}
+
+static void
+prepare_foreign_modify(PostgresFdwModifyState *fmstate)
+{
+	static int	prep_id = 1;
+	char		prep_name[NAMEDATALEN];
+	PGresult   *res;
+
+	snprintf(prep_name, sizeof(prep_name),
+			 "pgsql_fdw_prep_%08x", prep_id++);
+
+	res = PQprepare(fmstate->conn,
+					prep_name,
+					fmstate->query,
+					fmstate->p_nums,
+					fmstate->p_types);
+	if (!res || PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		PQclear(res);
+		elog(ERROR, "could not prepare statement (%s): %s",
+			 fmstate->query, PQerrorMessage(fmstate->conn));
+	}
+	PQclear(res);
+
+	fmstate->p_name = MemoryContextStrdup(fmstate->es_query_cxt, prep_name);
+}
+
+static int
+setup_exec_prepared(ResultRelInfo *resultRelInfo,
+					const char *rowid, TupleTableSlot *slot,
+					const char *p_values[], int p_lengths[])
+{
+	PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state;
+	int			pindex = 0;
+
+	/* 1st parameter should be ctid */
+	if (rowid)
+	{
+		p_values[pindex] = rowid;
+		p_lengths[pindex] = strlen(rowid) + 1;
+		pindex++;
+	}
+
+	/* following parameters are as TupleDesc */
+	if (slot != NULL)
+	{
+		TupleDesc	tupdesc = slot->tts_tupleDescriptor;
+		ListCell   *lc;
+
+		foreach (lc, fmstate->target_attrs)
+		{
+			Form_pg_attribute	attr = tupdesc->attrs[lfirst_int(lc) - 1];
+			Datum		value;
+			bool		isnull;
+
+			Assert(!attr->attisdropped);
+
+			value = slot_getattr(slot, attr->attnum, &isnull);
+			if (isnull)
+			{
+				p_values[pindex] = NULL;
+				p_lengths[pindex] = 0;
+			}
+			else
+			{
+				p_values[pindex] =
+					OutputFunctionCall(&fmstate->p_flinfo[pindex], value);
+				p_lengths[pindex] = strlen(p_values[pindex]) + 1;
+			}
+			pindex++;
+		}
+	}
+	return pindex;
+}
+
+static void
+store_returning_result(PostgresFdwModifyState *fmstate,
+					   TupleTableSlot *slot, PGresult *res)
+{
+	TupleDesc	tupdesc = slot->tts_tupleDescriptor;
+	AttrNumber	i, nattrs = tupdesc->natts;
+	Datum	   *values = alloca(sizeof(Datum) * nattrs);
+	bool	   *isnull = alloca(sizeof(bool) * nattrs);
+	HeapTuple	newtup;
+
+	memset(values, 0, sizeof(Datum) * nattrs);
+	memset(isnull, 0, sizeof(bool) * nattrs);
+
+	for (i=0; i < nattrs; i++)
+	{
+		Form_pg_attribute	attr = tupdesc->attrs[i];
+
+		if (attr->attisdropped || PQgetisnull(res, 0, i))
+			isnull[i] = true;
+		else
+		{
+			//elog(INFO, "col %d %s %d: value: %s fnoid: %u", i, NameStr(attr->attname), attr->attisdropped, PQgetvalue(res, 0, i), fmstate->r_flinfo[i].fn_oid);
+			values[i] = InputFunctionCall(&fmstate->r_flinfo[i],
+										  PQgetvalue(res, 0, i),
+										  fmstate->r_ioparam[i],
+										  attr->atttypmod);
+		}
+	}
+	newtup = heap_form_tuple(tupdesc, values, isnull);
+	ExecStoreTuple(newtup, slot, InvalidBuffer, false);
+}
+
+static TupleTableSlot *
+postgresExecForeignInsert(ResultRelInfo *resultRelInfo,
+						  TupleTableSlot *slot)
+{
+	PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state;
+	const char	  **p_values  = alloca(sizeof(char *) * fmstate->p_nums);
+	int			   *p_lengths = alloca(sizeof(int) * fmstate->p_nums);
+	AttrNumber		nattrs;
+	PGresult	   *res;
+	int				n_rows;
+
+	if (!fmstate->p_name)
+		prepare_foreign_modify(fmstate);
+
+	nattrs = setup_exec_prepared(resultRelInfo,
+								 NULL, slot,
+								 p_values, p_lengths);
+	Assert(fmstate->p_nums == nattrs);
+
+	res = PQexecPrepared(fmstate->conn,
+						 fmstate->p_name,
+						 nattrs,
+						 p_values,
+						 p_lengths,
+						 NULL, 0);
+	if (!res || (!fmstate->has_returning ?
+				 PQresultStatus(res) != PGRES_COMMAND_OK :
+				 PQresultStatus(res) != PGRES_TUPLES_OK))
+		elog(ERROR, "could not execute prepared statement (%s): %s",
+			 fmstate->query, PQerrorMessage(fmstate->conn));
+	n_rows = atoi(PQcmdTuples(res));
+	if (n_rows > 0 && fmstate->has_returning)
+		store_returning_result(fmstate, slot, res);
+	PQclear(res);
+
+	return (n_rows > 0 ? slot : NULL);
+}
+
+static bool
+postgresExecForeignDelete(ResultRelInfo *resultRelInfo, const char *rowid)
+{
+	PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state;
+	const char	   *p_values[1];
+	int				p_lengths[1];
+	AttrNumber		nattrs;
+	PGresult	   *res;
+	int				n_rows;
+
+	if (!fmstate->p_name)
+		prepare_foreign_modify(fmstate);
+
+	nattrs = setup_exec_prepared(resultRelInfo,
+								 rowid, NULL,
+								 p_values, p_lengths);
+	Assert(fmstate->p_nums == nattrs);
+
+	res = PQexecPrepared(fmstate->conn,
+						 fmstate->p_name,
+						 nattrs,
+						 p_values,
+						 p_lengths,
+						 NULL, 0);
+	if (!res ||  PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		PQclear(res);
+		elog(ERROR, "could not execute prepared statement (%s): %s",
+			 fmstate->query, PQerrorMessage(fmstate->conn));
+    }
+	n_rows = atoi(PQcmdTuples(res));
+    PQclear(res);
+
+	return (n_rows > 0 ? true : false);
+}
+
+static TupleTableSlot*
+postgresExecForeignUpdate(ResultRelInfo *resultRelInfo,
+						  const char *rowid, TupleTableSlot *slot)
+{
+	PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state;
+	const char	  **p_values  = alloca(sizeof(char *) * (fmstate->p_nums + 1));
+	int			   *p_lengths = alloca(sizeof(int) * (fmstate->p_nums + 1));
+	AttrNumber		nattrs;
+	PGresult	   *res;
+	int				n_rows;
+
+	if (!fmstate->p_name)
+		prepare_foreign_modify(fmstate);
+
+	nattrs = setup_exec_prepared(resultRelInfo,
+								 rowid, slot,
+								 p_values, p_lengths);
+	Assert(fmstate->p_nums == nattrs);
+
+	res = PQexecPrepared(fmstate->conn,
+						 fmstate->p_name,
+						 nattrs,
+						 p_values,
+						 p_lengths,
+						 NULL, 0);
+	if (!res || (!fmstate->has_returning ?
+				 PQresultStatus(res) != PGRES_COMMAND_OK :
+				 PQresultStatus(res) != PGRES_TUPLES_OK))
+	{
+		PQclear(res);
+		elog(ERROR, "could not execute prepared statement (%s): %s",
+			 fmstate->query, PQerrorMessage(fmstate->conn));
+	}
+	n_rows = atoi(PQcmdTuples(res));
+	if (n_rows > 0 && fmstate->has_returning)
+		store_returning_result(fmstate, slot, res);
+	PQclear(res);
+
+	return (n_rows > 0 ? slot : NULL);
+}
+
+static void
+postgresEndForeignModify(ResultRelInfo *resultRelInfo)
+{
+	PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state;
+
+	ReleaseConnection(fmstate->conn, false);
+	fmstate->conn = NULL;
+}
diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h
index b5cefb8..8225013 100644
--- a/contrib/postgres_fdw/postgres_fdw.h
+++ b/contrib/postgres_fdw/postgres_fdw.h
@@ -30,7 +30,8 @@ int GetFetchCountOption(ForeignTable *table, ForeignServer *server);
 void deparseSimpleSql(StringInfo buf,
 					  PlannerInfo *root,
 					  RelOptInfo *baserel,
-					  List *local_conds);
+					  List *local_conds,
+					  AttrNumber anum_rowid);
 void appendWhereClause(StringInfo buf,
 					   bool has_where,
 					   List *exprs,
@@ -41,5 +42,10 @@ void classifyConditions(PlannerInfo *root,
 						List **param_conds,
 						List **local_conds);
 void deparseAnalyzeSql(StringInfo buf, Relation rel);
+void deparseInsertSql(StringInfo buf, PlannerInfo *root, Index rtindex,
+					  List *targetAttrs, bool has_returning);
+void deparseUpdateSql(StringInfo buf, PlannerInfo *root, Index rtindex,
+					  List *targetAttrs, bool has_returning);
+void deparseDeleteSql(StringInfo buf, PlannerInfo *root, Index rtindex);
 
 #endif /* POSTGRESQL_FDW_H */
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 7845e70..89e6cf4 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -300,6 +300,37 @@ ERROR OUT;          -- ERROR
 SELECT srvname FROM postgres_fdw_connections;
 
 -- ===================================================================
+-- test for writable foreign table stuff (PoC stage now)
+-- ===================================================================
+EXPLAIN(verbose) 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) 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) 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;
+
+-- In case of remote table has before-row trigger or default with returning
+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 *;
+
+-- ===================================================================
 -- cleanup
 -- ===================================================================
 DROP OPERATOR === (int, int) CASCADE;
pgsql-v9.3-writable-fdw-poc.v11.part-1.patchapplication/octet-stream; name=pgsql-v9.3-writable-fdw-poc.v11.part-1.patchDownload
 doc/src/sgml/ddl.sgml                   |   5 -
 doc/src/sgml/fdwhandler.sgml            | 170 +++++++++++++++++++++++++++++++-
 src/backend/executor/execMain.c         |  34 ++++++-
 src/backend/executor/nodeForeignscan.c  | 136 ++++++++++++++++++++++++-
 src/backend/executor/nodeModifyTable.c  | 163 +++++++++++++++++++++++++-----
 src/backend/foreign/foreign.c           |  68 +++++++++++++
 src/backend/nodes/copyfuncs.c           |   2 +
 src/backend/nodes/outfuncs.c            |   2 +
 src/backend/optimizer/plan/createplan.c |  48 ++++++++-
 src/backend/optimizer/plan/initsplan.c  |  10 +-
 src/backend/optimizer/plan/planmain.c   |   5 +-
 src/backend/optimizer/plan/planner.c    |   8 +-
 src/backend/optimizer/prep/prepunion.c  |   3 +-
 src/backend/optimizer/util/plancat.c    |  27 ++++-
 src/backend/optimizer/util/relnode.c    |   7 +-
 src/backend/parser/parse_relation.c     |  15 ++-
 src/backend/rewrite/rewriteHandler.c    |  55 +++++++++--
 src/backend/utils/adt/ruleutils.c       |  16 ++-
 src/include/foreign/fdwapi.h            |  31 ++++++
 src/include/foreign/foreign.h           |   6 ++
 src/include/nodes/execnodes.h           |  13 ++-
 src/include/nodes/plannodes.h           |   2 +
 src/include/optimizer/pathnode.h        |   2 +-
 src/include/optimizer/plancat.h         |   2 +-
 src/include/optimizer/planmain.h        |   6 +-
 25 files changed, 766 insertions(+), 70 deletions(-)

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index f869b50..44db346 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -3067,11 +3067,6 @@ ANALYZE measurement;
     <firstterm>user mapping</>, which can provide additional options 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.
-   </para>
  </sect1>
 
  <sect1 id="ddl-others">
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index 638b6ab..731016f 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -89,6 +89,54 @@
 
     <para>
 <programlisting>
+AttrNumber
+GetForeignRelWidth(PlannerInfo *root,
+                   RelOptInfo *baserel,
+                   Relation foreignrel,
+                   bool inhparent,
+                   List *targetList);
+</programlisting>
+     Obtain the width of the result set to be fetched during a foreign table scan.
+     This is an optional handler, and called before <literal>GetForeignRelSize</>
+     for a query involving a foreign table
+     (during the construction of <literal>RelOptInfo</>).
+     <literal>root</> is the planner's global information about the query,
+     <literal>baserel</> is the planner's information being constructed for
+     this query, and <literal>foreignrel</> is a <literal>Relation</>
+     descriptor of the foreign table.
+     <literal>inhparent</> is a boolean to show whether the relation is
+     an inheritance parent, even though foreign tables do not support table
+     inheritance right now. <literal>targetList</> is the list of
+     <literal>TargetEntry</> to be returned from the (sub-)query
+     that is currently in focus.
+    </para>
+
+    <para>
+     The result value of this function will be assigned to
+     <literal>baserel-&gt;max_attr</>, that means it is the expected number
+     of columns being fetched during the foreign table scan.
+     It should not be smaller than the number of regular columns in the definition
+     of this foreign table.  You can only return a number greater than his value to
+     acquire slots for some additional attributes, which are called
+     <firstterm>pseudo-columns</>.
+     A typical usage of a pseudo-column is to carry an identifier of
+     a particular remote row to be updated or deleted from the scanning stage
+     to the modifying stage when the foreign table is the target of
+     a data-modifying SQL statement.
+     You can return the result of the helper function
+     <literal>get_pseudo_rowid_column</> if this <literal>"rowid"</>
+     pseudo-column is the only one you need.
+   </para>
+
+   <para>
+     In addition to that, pseudo-columns can be used to off-load the burden of
+     complex calculations to foreign computing resources by replacing an
+     expression with a reference to its result, which is calculated on the
+     remote side rather than locally.
+   </para>
+
+    <para>
+<programlisting>
 void
 GetForeignRelSize (PlannerInfo *root,
                    RelOptInfo *baserel,
@@ -96,7 +144,8 @@ 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.
+     for a query involving a foreign table at the beginning of planning
+     or right after <literal>GetForeignRelWidth</>, if that callback is configured.
      <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
@@ -315,6 +364,125 @@ AcquireSampleRowsFunc (Relation relation, int elevel,
     </para>
 
     <para>
+     If a FDW supports writable foreign tables, it should implement
+     some or all of the following callback functions depending on
+     the needs and capabilities of the FDW.
+    </para>
+
+    <para>
+<programlisting>
+List *
+PlanForeignModify(PlannerInfo *root,
+                  ModifyTable *plan,
+                  Index resultRelation,
+                  Plan *subplan);
+</programlisting>
+     It allows FDW drivers to construct private information relevant to
+     the modification of the foreign table.  This private information must have
+     the form of a <literal>List *</>, which will be delivered as
+     third argument to <literal>BeginForeignModify</> during the execution stage.
+    </para>
+
+    <para>
+     <literal>root</> is the planner's global information about the query.
+     <literal>plan</> is the master plan to modify the result relation according
+     to its command type.  Please consider that <literal>ModifyTable</>
+     may have multiple result relations in a future revision, even though
+     currently there is no support for table inheritance on foreign tables.
+     <literal>resultRelation</> is an index for the result relation in the
+     range table entries, and <literal>subplan</> is the relevant scan plan;
+     that should be a <literal>ForeignScan</> for <literal>UPDATE</> or
+     <literal>DELETE</>, so the driver can access the private information of
+     the scan stage using this argument.
+    </para>
+
+    <para>
+<programlisting>
+void
+BeginForeignModify (ModifyTableState *mtstate,
+                    ResultRelInfo *resultRelInfo,
+                    List *fdw_private,
+                    Plan *subplan,
+                    int eflags);
+</programlisting>
+     It is invoked at beginning of foreign table modification, during
+     executor startup.  This routine should perform any initialization
+     needed prior to the actual table modifications, but not start
+     modifying the actual tuples. (That should be done during each call of
+     <function>ExecForeignInsert</>, <function>ExecForeignUpdate</> or
+     <function>ExecForeignDelete</>.)
+    </para>
+
+    <para>
+     The <structfield>ri_fdw_state</> field of <structname>ResultRelInfo</>
+     is reserved to store any private information relevant to this foreign table,
+     so it should be initialized to contain the per-scan state.
+     Please consider that <literal>ModifyTableState</> may have multiple
+     result relations in a future revision, even though currently there is no
+     support for table inheritance on foreign tables.  <literal>resultRelInfo</>
+     is the master information connected to this foreign table.
+     <literal>fdw_private</> is private information constructed in
+     <literal>PlanForeignModify</>, and <literal>subplan</> is the relevant
+     scan plan of this table modification.
+    </para>
+
+    <para>
+<programlisting>
+TupleTableSlot *
+ExecForeignInsert (ResultRelInfo *resultRelInfo,
+                   TupleTableSlot *slot);
+</programlisting>
+     Insert the given tuple into backing storage on behalf of the foreign table.
+     The supplied slot shall hold a tuple being already formed according to
+     the definition of the relation, thus all the pseudo-columns are already
+     filtered out.
+     FDW driver can modify the tuple to be inserted (due to before-row-insert
+     triggers at remote side, for example). In this case, this routine can
+     return a modified slot that affects result of <literal>RETURNING</>,
+     or <literal>NULL</> that means this insertion was skipped. Elsewhere,
+     it usually returns the given slot, as is.
+    </para>
+
+    <para>
+<programlisting>
+bool
+ExecForeignDelete (ResultRelInfo *resultRelInfo,
+                   const char *rowid);
+</programlisting>
+     Delete the tuple being identified with <literal>rowid</> from the backing
+     storage on behalf of the foreign table.
+     FDW driver can return a boolean that means whether this deletion was done
+     actually, or not.
+    </para>
+
+    <para>
+<programlisting>
+TupleTableSlot *
+ExecForeignUpdate (ResultRelInfo *resultRelInfo,
+                   const char *rowid,
+                   TupleTableSlot *slot);
+</programlisting>
+     Update the tuple being identified with <literal>rowid</> in the backing
+     storage on behalf of the foreign table with the given slot that shall
+     hold a newer version of tuple.
+     FDW driver can modify the tuple to be updated (due to before-row-update
+     triggers at remote side, for example). In this case, this routine can
+     return a modified slot that affects result of <literal>RETURNING</>,
+     or <literal>NULL</> that means this update was skipped. Elsewhere,
+     it usually returns the given slot, as is.
+    </para>
+
+    <para>
+<programlisting>
+void
+EndForeignModify (ResultRelInfo *resultRelInfo);
+</programlisting>
+     End the modification 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>
      The <structname>FdwRoutine</> struct type is declared in
      <filename>src/include/foreign/fdwapi.h</>, which see for additional
      details.
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index e39911f..9f66481 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"
@@ -938,6 +939,7 @@ void
 CheckValidResultRel(Relation resultRel, CmdType operation)
 {
 	TriggerDesc *trigDesc = resultRel->trigdesc;
+	FdwRoutine	*fdwroutine;
 
 	switch (resultRel->rd_rel->relkind)
 	{
@@ -996,10 +998,34 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
 			}
 			break;
 		case RELKIND_FOREIGN_TABLE:
-			ereport(ERROR,
-					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("cannot change foreign table \"%s\"",
-							RelationGetRelationName(resultRel))));
+			fdwroutine = GetFdwRoutineByRelId(RelationGetRelid(resultRel));
+			switch (operation)
+			{
+				case CMD_INSERT:
+					if (!fdwroutine || !fdwroutine->ExecForeignInsert)
+						ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("cannot insert into foreign table \"%s\"",
+									RelationGetRelationName(resultRel))));
+					break;
+				case CMD_UPDATE:
+					if (!fdwroutine || !fdwroutine->ExecForeignUpdate)
+						ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("cannot update foreign table \"%s\"",
+									RelationGetRelationName(resultRel))));
+					break;
+				case CMD_DELETE:
+					if (!fdwroutine || !fdwroutine->ExecForeignDelete)
+						ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("cannot delete from foreign table \"%s\"",
+									RelationGetRelationName(resultRel))));
+					break;
+				default:
+					elog(ERROR, "unrecognized CmdType: %d", (int) operation);
+					break;
+			}
 			break;
 		default:
 			ereport(ERROR,
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 6ebffad..4eaf2a9 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -25,6 +25,7 @@
 #include "executor/executor.h"
 #include "executor/nodeForeignscan.h"
 #include "foreign/fdwapi.h"
+#include "nodes/nodeFuncs.h"
 #include "utils/rel.h"
 
 static TupleTableSlot *ForeignNext(ForeignScanState *node);
@@ -93,6 +94,133 @@ ExecForeignScan(ForeignScanState *node)
 					(ExecScanRecheckMtd) ForeignRecheck);
 }
 
+/*
+ * pseudo_column_walker
+ *
+ * helper routine of GetPseudoTupleDesc. It pulls Var nodes that reference
+ * pseudo columns from targetlis of the relation
+ */
+typedef struct
+{
+	Relation	relation;
+	Index		varno;
+	List	   *pcolumns;
+	AttrNumber	max_attno;
+} pseudo_column_walker_context;
+
+static bool
+pseudo_column_walker(Node *node, pseudo_column_walker_context *context)
+{
+	if (node == NULL)
+		return false;
+	if (IsA(node, Var))
+	{
+		Var		   *var = (Var *) node;
+		ListCell   *cell;
+
+		if (var->varno == context->varno && var->varlevelsup == 0 &&
+			var->varattno > RelationGetNumberOfAttributes(context->relation))
+		{
+			foreach (cell, context->pcolumns)
+			{
+				Var	   *temp = lfirst(cell);
+
+				if (temp->varattno == var->varattno)
+				{
+					if (!equal(var, temp))
+						elog(ERROR, "asymmetric pseudo column appeared");
+					break;
+				}
+			}
+			if (!cell)
+			{
+				context->pcolumns = lappend(context->pcolumns, var);
+				if (var->varattno > context->max_attno)
+					context->max_attno = var->varattno;
+			}
+		}
+		return false;
+	}
+
+	/* Should not find an unplanned subquery */
+	Assert(!IsA(node, Query));
+
+	return expression_tree_walker(node, pseudo_column_walker,
+								  (void *)context);
+}
+
+/*
+ * GetPseudoTupleDesc
+ *
+ * It generates TupleDesc structure including pseudo-columns if required.
+ */
+static TupleDesc
+GetPseudoTupleDesc(ForeignScan *node, Relation relation)
+{
+	pseudo_column_walker_context context;
+	List	   *target_list = node->scan.plan.targetlist;
+	TupleDesc	tupdesc;
+	AttrNumber	attno;
+	ListCell   *cell;
+	ListCell   *prev;
+	bool		hasoid;
+
+	context.relation = relation;
+	context.varno = node->scan.scanrelid;
+	context.pcolumns = NIL;
+	context.max_attno = -1;
+
+	pseudo_column_walker((Node *)target_list, (void *)&context);
+	Assert(context.max_attno > RelationGetNumberOfAttributes(relation));
+
+	hasoid = RelationGetForm(relation)->relhasoids;
+	tupdesc = CreateTemplateTupleDesc(context.max_attno, hasoid);
+
+	for (attno = 1; attno <= context.max_attno; attno++)
+	{
+		/* case of regular columns */
+		if (attno <= RelationGetNumberOfAttributes(relation))
+		{
+			memcpy(tupdesc->attrs[attno - 1],
+				   RelationGetDescr(relation)->attrs[attno - 1],
+				   ATTRIBUTE_FIXED_PART_SIZE);
+			continue;
+		}
+
+		/* case of pseudo columns */
+		prev = NULL;
+		foreach (cell, context.pcolumns)
+		{
+			Var	   *var = lfirst(cell);
+
+			if (var->varattno == attno)
+			{
+				char		namebuf[NAMEDATALEN];
+
+				snprintf(namebuf, sizeof(namebuf),
+						 "pseudo_column_%d", attno);
+
+				TupleDescInitEntry(tupdesc,
+								   attno,
+								   namebuf,
+								   var->vartype,
+								   var->vartypmod,
+								   0);
+				TupleDescInitEntryCollation(tupdesc,
+											attno,
+											var->varcollid);
+				context.pcolumns
+					= list_delete_cell(context.pcolumns, cell, prev);
+				break;
+			}
+			prev = cell;
+		}
+		if (!cell)
+			elog(ERROR, "pseudo column %d of %s not in target list",
+				 attno, RelationGetRelationName(relation));
+	}
+	return tupdesc;
+}
 
 /* ----------------------------------------------------------------
  *		ExecInitForeignScan
@@ -103,6 +231,7 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
 {
 	ForeignScanState *scanstate;
 	Relation	currentRelation;
+	TupleDesc	tupdesc;
 	FdwRoutine *fdwroutine;
 
 	/* check for unsupported flags */
@@ -149,7 +278,12 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
 	/*
 	 * get the scan type from the relation descriptor.
 	 */
-	ExecAssignScanType(&scanstate->ss, RelationGetDescr(currentRelation));
+	if (node->fsPseudoCol)
+		tupdesc = GetPseudoTupleDesc(node, currentRelation);
+	else
+		tupdesc = RelationGetDescr(currentRelation);
+
+	ExecAssignScanType(&scanstate->ss, tupdesc);
 
 	/*
 	 * Initialize result tuple type and projection info.
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index cb084d0..9572b3a 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,20 @@ ExecInsert(TupleTableSlot *slot,
 
 		newId = InvalidOid;
 	}
+	else if (resultRelInfo->ri_fdwroutine)
+	{
+		FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+		slot = fdwroutine->ExecForeignInsert(resultRelInfo, slot);
+
+		if (slot == NULL)		/* "do nothing" */
+			return NULL;
+
+		/* FDW driver might have changed tuple */
+		tuple = ExecMaterializeSlot(slot);
+
+		newId = InvalidOid;
+	}
 	else
 	{
 		/*
@@ -252,7 +267,7 @@ ExecInsert(TupleTableSlot *slot,
 
 	if (canSetTag)
 	{
-		(estate->es_processed)++;
+		(estate->es_processed) ++;
 		estate->es_lastoid = newId;
 		setLastTid(&(tuple->t_self));
 	}
@@ -285,7 +300,7 @@ ExecInsert(TupleTableSlot *slot,
  * ----------------------------------------------------------------
  */
 static TupleTableSlot *
-ExecDelete(ItemPointer tupleid,
+ExecDelete(Datum rowid,
 		   HeapTupleHeader oldtuple,
 		   TupleTableSlot *planSlot,
 		   EPQState *epqstate,
@@ -310,7 +325,7 @@ ExecDelete(ItemPointer tupleid,
 		bool		dodelete;
 
 		dodelete = ExecBRDeleteTriggers(estate, epqstate, resultRelInfo,
-										tupleid);
+										(ItemPointer)DatumGetPointer(rowid));
 
 		if (!dodelete)			/* "do nothing" */
 			return NULL;
@@ -334,8 +349,20 @@ ExecDelete(ItemPointer tupleid,
 		if (!dodelete)			/* "do nothing" */
 			return NULL;
 	}
+	else if (resultRelInfo->ri_fdwroutine)
+	{
+		FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+		bool		dodelete;
+
+		dodelete = fdwroutine->ExecForeignDelete(resultRelInfo,
+												 DatumGetCString(rowid));
+		if (!dodelete)
+			return NULL;		/* "do nothing" */
+	}
 	else
 	{
+		ItemPointer	tupleid = (ItemPointer) DatumGetPointer(rowid);
+
 		/*
 		 * delete the tuple
 		 *
@@ -431,10 +458,11 @@ ldelete:;
 	}
 
 	if (canSetTag)
-		(estate->es_processed)++;
+		(estate->es_processed) ++;
 
 	/* AFTER ROW DELETE Triggers */
-	ExecARDeleteTriggers(estate, resultRelInfo, tupleid);
+	ExecARDeleteTriggers(estate, resultRelInfo,
+						 (ItemPointer)DatumGetPointer(rowid));
 
 	/* Process RETURNING if present */
 	if (resultRelInfo->ri_projectReturning)
@@ -458,7 +486,8 @@ ldelete:;
 		}
 		else
 		{
-			deltuple.t_self = *tupleid;
+			ItemPointerCopy((ItemPointer)DatumGetPointer(rowid),
+							&deltuple.t_self);
 			if (!heap_fetch(resultRelationDesc, SnapshotAny,
 							&deltuple, &delbuffer, false, NULL))
 				elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
@@ -500,7 +529,7 @@ ldelete:;
  * ----------------------------------------------------------------
  */
 static TupleTableSlot *
-ExecUpdate(ItemPointer tupleid,
+ExecUpdate(Datum rowid,
 		   HeapTupleHeader oldtuple,
 		   TupleTableSlot *slot,
 		   TupleTableSlot *planSlot,
@@ -538,7 +567,7 @@ ExecUpdate(ItemPointer tupleid,
 		resultRelInfo->ri_TrigDesc->trig_update_before_row)
 	{
 		slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
-									tupleid, slot);
+									(ItemPointer)DatumGetPointer(rowid), slot);
 
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
@@ -568,9 +597,23 @@ ExecUpdate(ItemPointer tupleid,
 		/* trigger might have changed tuple */
 		tuple = ExecMaterializeSlot(slot);
 	}
+	else if (resultRelInfo->ri_fdwroutine)
+	{
+		FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+		slot = fdwroutine->ExecForeignUpdate(resultRelInfo,
+											 DatumGetCString(rowid),
+											 slot);
+		if (slot == NULL)		/* "do nothing" */
+			return NULL;
+
+		/* FDW driver might have changed tuple */
+		tuple = ExecMaterializeSlot(slot);
+	}
 	else
 	{
 		LockTupleMode	lockmode;
+		ItemPointer	tupleid = (ItemPointer) DatumGetPointer(rowid);
 
 		/*
 		 * Check the constraints of the tuple
@@ -691,11 +734,12 @@ lreplace:;
 	}
 
 	if (canSetTag)
-		(estate->es_processed)++;
+		(estate->es_processed) ++;
 
 	/* AFTER ROW UPDATE Triggers */
-	ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple,
-						 recheckIndexes);
+	ExecARUpdateTriggers(estate, resultRelInfo,
+						 (ItemPointer) DatumGetPointer(rowid),
+						 tuple, recheckIndexes);
 
 	list_free(recheckIndexes);
 
@@ -775,6 +819,7 @@ ExecModifyTable(ModifyTableState *node)
 	TupleTableSlot *planSlot;
 	ItemPointer tupleid = NULL;
 	ItemPointerData tuple_ctid;
+	Datum		rowid = 0;
 	HeapTupleHeader oldtuple = NULL;
 
 	/*
@@ -863,17 +908,19 @@ ExecModifyTable(ModifyTableState *node)
 		if (junkfilter != NULL)
 		{
 			/*
-			 * extract the 'ctid' or 'wholerow' junk attribute.
+			 * extract the 'ctid', 'rowid' or 'wholerow' junk attribute.
 			 */
 			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,
+												 junkfilter->jf_junkRowidNo,
 												 &isNull);
 					/* shouldn't ever get a null result... */
 					if (isNull)
@@ -881,13 +928,33 @@ ExecModifyTable(ModifyTableState *node)
 
 					tupleid = (ItemPointer) DatumGetPointer(datum);
 					tuple_ctid = *tupleid;		/* be sure we don't free
-												 * ctid!! */
-					tupleid = &tuple_ctid;
+												 * ctid ! */
+					rowid = PointerGetDatum(&tuple_ctid);
+				}
+				else if (relkind == RELKIND_FOREIGN_TABLE)
+				{
+					datum = ExecGetJunkAttribute(slot,
+												 junkfilter->jf_junkRowidNo,
+												 &isNull);
+					/* shouldn't ever get a null result... */
+					if (isNull)
+						elog(ERROR, "rowid is NULL");
+
+					rowid = datum;
+
+					datum = ExecGetJunkAttribute(slot,
+												 junkfilter->jf_junkRecordNo,
+												 &isNull);
+					/* shouldn't ever get a null result... */
+					if (isNull)
+						elog(ERROR, "wholerow is NULL");
+
+					oldtuple = DatumGetHeapTupleHeader(datum);
 				}
 				else
 				{
 					datum = ExecGetJunkAttribute(slot,
-												 junkfilter->jf_junkAttNo,
+												 junkfilter->jf_junkRecordNo,
 												 &isNull);
 					/* shouldn't ever get a null result... */
 					if (isNull)
@@ -910,11 +977,11 @@ ExecModifyTable(ModifyTableState *node)
 				slot = ExecInsert(slot, planSlot, estate, node->canSetTag);
 				break;
 			case CMD_UPDATE:
-				slot = ExecUpdate(tupleid, oldtuple, slot, planSlot,
+				slot = ExecUpdate(rowid, oldtuple, slot, planSlot,
 								&node->mt_epqstate, estate, node->canSetTag);
 				break;
 			case CMD_DELETE:
-				slot = ExecDelete(tupleid, oldtuple, planSlot,
+				slot = ExecDelete(rowid, oldtuple, planSlot,
 								&node->mt_epqstate, estate, node->canSetTag);
 				break;
 			default:
@@ -1001,6 +1068,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	i = 0;
 	foreach(l, node->plans)
 	{
+		char	relkind;
+
 		subplan = (Plan *) lfirst(l);
 
 		/*
@@ -1026,6 +1095,24 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		estate->es_result_relation_info = resultRelInfo;
 		mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
 
+		/*
+		 * Also tells FDW extensions to init the plan for this result rel
+		 */
+		relkind = RelationGetForm(resultRelInfo->ri_RelationDesc)->relkind;
+		if (relkind == RELKIND_FOREIGN_TABLE)
+		{
+			Oid		relid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+			FdwRoutine *fdwroutine = GetFdwRoutineByRelId(relid);
+			List   *fdwprivate = list_nth(node->fdwPrivList, i);
+
+			Assert(fdwroutine != NULL);
+			resultRelInfo->ri_fdwroutine = fdwroutine;
+			resultRelInfo->ri_fdw_state = NULL;
+
+			if (fdwroutine->BeginForeignModify)
+				fdwroutine->BeginForeignModify(mtstate, resultRelInfo,
+											   fdwprivate, subplan, eflags);
+		}
 		resultRelInfo++;
 		i++;
 	}
@@ -1167,6 +1254,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			for (i = 0; i < nplans; i++)
 			{
 				JunkFilter *j;
+				char		relkind =
+				    RelationGetForm(resultRelInfo->ri_RelationDesc)->relkind;
 
 				subplan = mtstate->mt_plans[i]->plan;
 				if (operation == CMD_INSERT || operation == CMD_UPDATE)
@@ -1180,16 +1269,27 @@ 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)
+					if (relkind == RELKIND_RELATION)
 					{
-						j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
-						if (!AttributeNumberIsValid(j->jf_junkAttNo))
+						j->jf_junkRowidNo = ExecFindJunkAttribute(j, "ctid");
+						if (!AttributeNumberIsValid(j->jf_junkRowidNo))
 							elog(ERROR, "could not find junk ctid column");
 					}
+					else if (relkind == RELKIND_FOREIGN_TABLE)
+					{
+						j->jf_junkRowidNo = ExecFindJunkAttribute(j, "rowid");
+						if (!AttributeNumberIsValid(j->jf_junkRowidNo))
+							elog(ERROR, "could not find junk rowid column");
+						j->jf_junkRecordNo
+							= ExecFindJunkAttribute(j, "record");
+						if (!AttributeNumberIsValid(j->jf_junkRecordNo))
+							elog(ERROR, "could not find junk wholerow column");
+					}
 					else
 					{
-						j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
-						if (!AttributeNumberIsValid(j->jf_junkAttNo))
+						j->jf_junkRecordNo
+							= ExecFindJunkAttribute(j, "wholerow");
+						if (!AttributeNumberIsValid(j->jf_junkRecordNo))
 							elog(ERROR, "could not find junk wholerow column");
 					}
 				}
@@ -1243,6 +1343,21 @@ ExecEndModifyTable(ModifyTableState *node)
 {
 	int			i;
 
+	/* Let the FDW shut dowm */
+	for (i=0; i < node->ps.state->es_num_result_relations; i++)
+	{
+		ResultRelInfo  *resultRelInfo
+			= &node->ps.state->es_result_relations[i];
+
+		if (resultRelInfo->ri_fdwroutine)
+		{
+			FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+			if (fdwroutine->EndForeignModify)
+				fdwroutine->EndForeignModify(resultRelInfo);
+		}
+	}
+
 	/*
 	 * Free the exprcontext
 	 */
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
index 872ed1f..cdeeb03 100644
--- a/src/backend/foreign/foreign.c
+++ b/src/backend/foreign/foreign.c
@@ -571,3 +571,71 @@ get_foreign_server_oid(const char *servername, bool missing_ok)
 				 errmsg("server \"%s\" does not exist", servername)));
 	return oid;
 }
+
+/*
+ * get_pseudo_rowid_column
+ *
+ * It picks up an attribute number to be used for the pseudo rowid column,
+ * if it exists.  It should be injected at rewriteHandler.c if the supplied query
+ * is a UPDATE or DELETE command.  Elsewhere, it returns InvalidAttrNumber.
+ */
+AttrNumber
+get_pseudo_rowid_column(RelOptInfo *baserel, List *targetList)
+{
+	ListCell   *cell;
+
+	foreach (cell, targetList)
+	{
+		TargetEntry *tle = lfirst(cell);
+
+		if (tle->resjunk &&
+			tle->resname && strcmp(tle->resname, "rowid") == 0)
+		{
+			Var	   *var;
+
+			if (!IsA(tle->expr, Var))
+				elog(ERROR, "unexpected node on junk rowid entry: %d",
+					 (int) nodeTag(tle->expr));
+
+			var = (Var *) tle->expr;
+			if (baserel->relid == var->varno)
+				return var->varattno;
+		}
+	}
+	return InvalidAttrNumber;
+}
+
+/*
+ * lookup_foreign_scan_plan
+ *
+ * It looks up the ForeignScan plan node with the supplied range-table id.
+ * Its typical usage is for PlanForeignModify to get the underlying scan plan on
+ * UPDATE or DELETE commands.
+ */
+ForeignScan *
+lookup_foreign_scan_plan(Plan *subplan, Index rtindex)
+{
+	if (!subplan)
+		return NULL;
+
+	else if (IsA(subplan, ForeignScan))
+	{
+		ForeignScan	   *fscan = (ForeignScan *) subplan;
+
+		if (fscan->scan.scanrelid == rtindex)
+			return fscan;
+	}
+	else
+	{
+		ForeignScan    *fscan;
+
+		fscan = lookup_foreign_scan_plan(subplan->lefttree, rtindex);
+		if (fscan != NULL)
+			return fscan;
+
+		fscan = lookup_foreign_scan_plan(subplan->righttree, rtindex);
+		if (fscan != NULL)
+			return fscan;
+	}
+	return NULL;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 2da08d1..3593ddf 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -181,6 +181,7 @@ _copyModifyTable(const ModifyTable *from)
 	COPY_NODE_FIELD(returningLists);
 	COPY_NODE_FIELD(rowMarks);
 	COPY_SCALAR_FIELD(epqParam);
+	COPY_NODE_FIELD(fdwPrivList);
 
 	return newnode;
 }
@@ -594,6 +595,7 @@ _copyForeignScan(const ForeignScan *from)
 	COPY_NODE_FIELD(fdw_exprs);
 	COPY_NODE_FIELD(fdw_private);
 	COPY_SCALAR_FIELD(fsSystemCol);
+	COPY_SCALAR_FIELD(fsPseudoCol);
 
 	return newnode;
 }
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index ffd123d..6ab2dcd 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -335,6 +335,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
 	WRITE_NODE_FIELD(returningLists);
 	WRITE_NODE_FIELD(rowMarks);
 	WRITE_INT_FIELD(epqParam);
+	WRITE_NODE_FIELD(fdwPrivList);
 }
 
 static void
@@ -562,6 +563,7 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
 	WRITE_NODE_FIELD(fdw_exprs);
 	WRITE_NODE_FIELD(fdw_private);
 	WRITE_BOOL_FIELD(fsSystemCol);
+	WRITE_BOOL_FIELD(fsPseudoCol);
 }
 
 static void
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 8a51b56..5082ad8 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -39,6 +39,7 @@
 #include "parser/parse_clause.h"
 #include "parser/parsetree.h"
 #include "utils/lsyscache.h"
+#include "utils/rel.h"
 
 
 static Plan *create_plan_recurse(PlannerInfo *root, Path *best_path);
@@ -1943,6 +1944,8 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 	RelOptInfo *rel = best_path->path.parent;
 	Index		scan_relid = rel->relid;
 	RangeTblEntry *rte;
+	Relation	relation;
+	AttrNumber	num_attrs;
 	int			i;
 
 	/* it should be a base rel... */
@@ -2001,6 +2004,22 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 		}
 	}
 
+	/*
+	 * Also, detect whether any pseudo columns are requested from rel.
+	 */
+	relation = heap_open(rte->relid, NoLock);
+	scan_plan->fsPseudoCol = false;
+	num_attrs = RelationGetNumberOfAttributes(relation);
+	for (i = num_attrs + 1; i <= rel->max_attr; i++)
+	{
+		if (!bms_is_empty(rel->attr_needed[i - rel->min_attr]))
+		{
+			scan_plan->fsPseudoCol = true;
+			break;
+		}
+	}
+	heap_close(relation, NoLock);
+
 	return scan_plan;
 }
 
@@ -4695,7 +4714,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)
@@ -4704,6 +4724,8 @@ make_modifytable(CmdType operation, bool canSetTag,
 	Plan	   *plan = &node->plan;
 	double		total_size;
 	ListCell   *subnode;
+	ListCell   *resultRel;
+	List	   *fdw_priv_list = NIL;
 
 	Assert(list_length(resultRelations) == list_length(subplans));
 	Assert(returningLists == NIL ||
@@ -4746,6 +4768,30 @@ make_modifytable(CmdType operation, bool canSetTag,
 	node->rowMarks = rowMarks;
 	node->epqParam = epqParam;
 
+	/*
+	 * Allow FDW driver to construct its private plan if the result relation
+	 * is a foreign table.
+	 */
+	forboth (resultRel, resultRelations, subnode, subplans)
+	{
+		RangeTblEntry  *rte = rt_fetch(lfirst_int(resultRel),
+									   root->parse->rtable);
+		List		   *fdw_private = NIL;
+		char			relkind = get_rel_relkind(rte->relid);
+
+		if (relkind == RELKIND_FOREIGN_TABLE)
+		{
+			FdwRoutine *fdwroutine = GetFdwRoutineByRelId(rte->relid);
+
+			if (fdwroutine && fdwroutine->PlanForeignModify)
+				fdw_private = fdwroutine->PlanForeignModify(root, node,
+													lfirst_int(resultRel),
+													(Plan *) lfirst(subnode));
+		}
+		fdw_priv_list = lappend(fdw_priv_list, fdw_private);
+	}
+	node->fdwPrivList = fdw_priv_list;
+
 	return node;
 }
 
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 11d951c..bbfcd83 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -85,7 +85,7 @@ static void check_hashjoinable(RestrictInfo *restrictinfo);
  * "other rel" RelOptInfos for the members of any appendrels we find here.)
  */
 void
-add_base_rels_to_query(PlannerInfo *root, Node *jtnode)
+add_base_rels_to_query(PlannerInfo *root, List *tlist, Node *jtnode)
 {
 	if (jtnode == NULL)
 		return;
@@ -93,7 +93,7 @@ add_base_rels_to_query(PlannerInfo *root, Node *jtnode)
 	{
 		int			varno = ((RangeTblRef *) jtnode)->rtindex;
 
-		(void) build_simple_rel(root, varno, RELOPT_BASEREL);
+		(void) build_simple_rel(root, varno, tlist, RELOPT_BASEREL);
 	}
 	else if (IsA(jtnode, FromExpr))
 	{
@@ -101,14 +101,14 @@ add_base_rels_to_query(PlannerInfo *root, Node *jtnode)
 		ListCell   *l;
 
 		foreach(l, f->fromlist)
-			add_base_rels_to_query(root, lfirst(l));
+			add_base_rels_to_query(root, tlist, lfirst(l));
 	}
 	else if (IsA(jtnode, JoinExpr))
 	{
 		JoinExpr   *j = (JoinExpr *) jtnode;
 
-		add_base_rels_to_query(root, j->larg);
-		add_base_rels_to_query(root, j->rarg);
+		add_base_rels_to_query(root, tlist, j->larg);
+		add_base_rels_to_query(root, tlist, j->rarg);
 	}
 	else
 		elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
index a919914..0f26f6f 100644
--- a/src/backend/optimizer/plan/planmain.c
+++ b/src/backend/optimizer/plan/planmain.c
@@ -112,6 +112,9 @@ query_planner(PlannerInfo *root, List *tlist,
 	 */
 	if (parse->jointree->fromlist == NIL)
 	{
+		/* Make a flattened version of the rangetable for faster access */
+		setup_simple_rel_arrays(root);
+
 		/* We need a trivial path result */
 		*cheapest_path = (Path *)
 			create_result_path((List *) parse->jointree->quals);
@@ -163,7 +166,7 @@ query_planner(PlannerInfo *root, List *tlist,
 	 * rangetable may contain RTEs for rels not actively part of the query,
 	 * for example views.  We don't want to make RelOptInfos for them.
 	 */
-	add_base_rels_to_query(root, (Node *) parse->jointree);
+	add_base_rels_to_query(root, tlist, (Node *) parse->jointree);
 
 	/*
 	 * Examine the targetlist and join tree, adding entries to baserel
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 3e75d39..2809f49 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,
@@ -3396,7 +3398,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 	setup_simple_rel_arrays(root);
 
 	/* Build RelOptInfo */
-	rel = build_simple_rel(root, 1, RELOPT_BASEREL);
+	rel = build_simple_rel(root, 1, NIL, RELOPT_BASEREL);
 
 	/* Locate IndexOptInfo for the target index */
 	indexInfo = NULL;
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index e249628..e2984a5 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -236,7 +236,8 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 		 * used for anything here, but it carries the subroot data structures
 		 * forward to setrefs.c processing.
 		 */
-		rel = build_simple_rel(root, rtr->rtindex, RELOPT_BASEREL);
+		rel = build_simple_rel(root, rtr->rtindex, refnames_tlist,
+							   RELOPT_BASEREL);
 
 		/* plan_params should not be in use in current query level */
 		Assert(root->plan_params == NIL);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 01f988f..7e98cea 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -26,6 +26,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/heap.h"
+#include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/clauses.h"
@@ -81,7 +82,7 @@ static List *build_index_tlist(PlannerInfo *root, IndexOptInfo *index,
  */
 void
 get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
-				  RelOptInfo *rel)
+				  List *tlist, RelOptInfo *rel)
 {
 	Index		varno = rel->relid;
 	Relation	relation;
@@ -105,6 +106,30 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 	rel->max_attr = RelationGetNumberOfAttributes(relation);
 	rel->reltablespace = RelationGetForm(relation)->reltablespace;
 
+	/*
+	 * Adjust width of attr_needed slot in case FDW extension wants
+	 * to return pseudo-columns in addition to the columns in its
+	 * table definition.
+	 * GetForeignRelWidth, an optional FDW handler, enables a FDW
+	 * to save properties of a pseudo-column in its private field.
+	 * When the foreign table is the target of UPDATE/DELETE, the query rewriter
+	 * injects a "rowid" pseudo-column to track the remote row to be modified,
+	 * so the FDW has to track which varattno shall perform as "rowid".
+	 */
+	if (RelationGetForm(relation)->relkind == RELKIND_FOREIGN_TABLE)
+	{
+		FdwRoutine *fdwroutine = GetFdwRoutineByRelId(relationObjectId);
+
+		if (fdwroutine->GetForeignRelWidth)
+		{
+			rel->max_attr = fdwroutine->GetForeignRelWidth(root, rel,
+														   relation,
+														   inhparent,
+														   tlist);
+			Assert(rel->max_attr >= RelationGetNumberOfAttributes(relation));
+		}
+	}
+
 	Assert(rel->max_attr >= rel->min_attr);
 	rel->attr_needed = (Relids *)
 		palloc0((rel->max_attr - rel->min_attr + 1) * sizeof(Relids));
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 8ee5671..ababef5 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -80,7 +80,8 @@ setup_simple_rel_arrays(PlannerInfo *root)
  *	  Construct a new RelOptInfo for a base relation or 'other' relation.
  */
 RelOptInfo *
-build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
+build_simple_rel(PlannerInfo *root, int relid, List *tlist,
+				 RelOptKind reloptkind)
 {
 	RelOptInfo *rel;
 	RangeTblEntry *rte;
@@ -133,7 +134,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
 	{
 		case RTE_RELATION:
 			/* Table --- retrieve statistics from the system catalogs */
-			get_relation_info(root, rte->relid, rte->inh, rel);
+			get_relation_info(root, rte->relid, rte->inh, tlist, rel);
 			break;
 		case RTE_SUBQUERY:
 		case RTE_FUNCTION:
@@ -180,7 +181,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
 			if (appinfo->parent_relid != relid)
 				continue;
 
-			(void) build_simple_rel(root, appinfo->child_relid,
+			(void) build_simple_rel(root, appinfo->child_relid, tlist,
 									RELOPT_OTHER_MEMBER_REL);
 		}
 	}
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 93aeab8..f246d43 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -2087,7 +2087,20 @@ get_rte_attribute_name(RangeTblEntry *rte, AttrNumber attnum)
 	 * built (which can easily happen for rules).
 	 */
 	if (rte->rtekind == RTE_RELATION)
-		return get_relid_attribute_name(rte->relid, attnum);
+	{
+		char   *attname = get_attname(rte->relid, attnum);
+
+		if (attname)
+			return attname;
+
+		/*
+		 * XXX - If FDW driver adds pseudo-columns, it may have attribute
+		 * number larger than number of relation's attribute. In this case,
+		 * get_attname() returns NULL and we fall back on alias list on eref.
+		 * It should not happen other than foreign tables.
+		 */
+		Assert(get_rel_relkind(rte->relid) == RELKIND_FOREIGN_TABLE);
+	}
 
 	/*
 	 * Otherwise use the column name from eref.  There should always be one.
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index b458de6..f6494d8 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1165,7 +1165,10 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 					Relation target_relation)
 {
 	Var		   *var;
-	const char *attrname;
+	List	   *varList;
+	List	   *attNameList;
+	ListCell   *cell1;
+	ListCell   *cell2;
 	TargetEntry *tle;
 
 	if (target_relation->rd_rel->relkind == RELKIND_RELATION)
@@ -1179,8 +1182,35 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 					  -1,
 					  InvalidOid,
 					  0);
+		varList = list_make1(var);
+		attNameList = list_make1("ctid");
+	}
+	else if (target_relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+	{
+		/*
+		 * Emit Rowid so that executor can find the row to update or delete.
+		 */
+		var = makeVar(parsetree->resultRelation,
+					  RelationGetNumberOfAttributes(target_relation) + 1,
+					  CSTRINGOID,
+					  -2,
+					  InvalidOid,
+					  0);
+		varList = list_make1(var);
+
+		/*
+		 * Emit generic record Var so that executor will have the "old" view
+		 * row to pass the RETURNING clause (or upcoming triggers).
+		 */
+		var = makeVar(parsetree->resultRelation,
+					  InvalidAttrNumber,
+					  RECORDOID,
+					  -1,
+					  InvalidOid,
+					  0);
+		varList = lappend(varList, var);
 
-		attrname = "ctid";
+		attNameList = list_make2("rowid", "record");
 	}
 	else
 	{
@@ -1192,16 +1222,21 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 							  parsetree->resultRelation,
 							  0,
 							  false);
-
-		attrname = "wholerow";
+		varList = list_make1(var);
+		attNameList = list_make1("wholerow");
 	}
 
-	tle = makeTargetEntry((Expr *) var,
-						  list_length(parsetree->targetList) + 1,
-						  pstrdup(attrname),
-						  true);
-
-	parsetree->targetList = lappend(parsetree->targetList, tle);
+	/*
+	 * Append them to targetList
+	 */
+	forboth (cell1, varList, cell2, attNameList)
+	{
+		tle = makeTargetEntry((Expr *)lfirst(cell1),
+							  list_length(parsetree->targetList) + 1,
+							  pstrdup((const char *)lfirst(cell2)),
+							  true);
+		parsetree->targetList = lappend(parsetree->targetList, tle);
+	}
 }
 
 
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 16f56c6..8d26e47 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -2857,16 +2857,28 @@ set_relation_column_names(deparse_namespace *dpns, RangeTblEntry *rte,
 		rel = relation_open(rte->relid, AccessShareLock);
 		tupdesc = RelationGetDescr(rel);
 
-		ncolumns = tupdesc->natts;
+		ncolumns = (tupdesc->natts < list_length(rte->eref->colnames)
+					? list_length(rte->eref->colnames)
+					: tupdesc->natts);
 		real_colnames = (char **) palloc(ncolumns * sizeof(char *));
 
-		for (i = 0; i < ncolumns; i++)
+		for (i = 0; i < tupdesc->natts; i++)
 		{
 			if (tupdesc->attrs[i]->attisdropped)
 				real_colnames[i] = NULL;
 			else
 				real_colnames[i] = pstrdup(NameStr(tupdesc->attrs[i]->attname));
 		}
+		/*
+		 * XXX - foreign table may have pseudo-column that has attribute
+		 * number larger than or equal with number of attributes in table
+		 * definition. So, we need to add entries for them.
+		 */
+		while (i < ncolumns)
+		{
+			real_colnames[i] = strVal(list_nth(rte->eref->colnames, i));
+			i++;
+		}
 		relation_close(rel, AccessShareLock);
 	}
 	else
diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
index 13dcbfd..fd99591 100644
--- a/src/include/foreign/fdwapi.h
+++ b/src/include/foreign/fdwapi.h
@@ -14,6 +14,7 @@
 
 #include "nodes/execnodes.h"
 #include "nodes/relation.h"
+#include "utils/rel.h"
 
 /* To avoid including explain.h here, reference ExplainState thus: */
 struct ExplainState;
@@ -22,6 +23,11 @@ struct ExplainState;
 /*
  * Callback function signatures --- see fdwhandler.sgml for more info.
  */
+typedef AttrNumber (*GetForeignRelWidth_function) (PlannerInfo *root,
+												   RelOptInfo *baserel,
+												   Relation foreignrel,
+												   bool inhparent,
+												   List *targetList);
 
 typedef void (*GetForeignRelSize_function) (PlannerInfo *root,
 														RelOptInfo *baserel,
@@ -58,6 +64,24 @@ typedef int (*AcquireSampleRowsFunc) (Relation relation, int elevel,
 typedef bool (*AnalyzeForeignTable_function) (Relation relation,
 												 AcquireSampleRowsFunc *func,
 													BlockNumber *totalpages);
+typedef List *(*PlanForeignModify_function) (PlannerInfo *root,
+											 ModifyTable *plan,
+											 Index resultRelation,
+											 Plan *subplan);
+
+typedef void (*BeginForeignModify_function) (ModifyTableState *mtstate,
+											 ResultRelInfo *rinfo,
+											 List *fdw_private,
+											 Plan *subplan,
+											 int eflags);
+typedef TupleTableSlot *(*ExecForeignInsert_function) (ResultRelInfo *rinfo,
+													   TupleTableSlot *slot);
+typedef TupleTableSlot *(*ExecForeignUpdate_function) (ResultRelInfo *rinfo,
+													   const char *rowid,
+													   TupleTableSlot *slot);
+typedef bool (*ExecForeignDelete_function) (ResultRelInfo *rinfo,
+											const char *rowid);
+typedef void (*EndForeignModify_function) (ResultRelInfo *rinfo);
 
 /*
  * FdwRoutine is the struct returned by a foreign-data wrapper's handler
@@ -90,6 +114,13 @@ typedef struct FdwRoutine
 	 * not provided.
 	 */
 	AnalyzeForeignTable_function AnalyzeForeignTable;
+	GetForeignRelWidth_function GetForeignRelWidth;
+	PlanForeignModify_function PlanForeignModify;
+	BeginForeignModify_function	BeginForeignModify;
+	ExecForeignInsert_function ExecForeignInsert;
+	ExecForeignDelete_function ExecForeignDelete;
+	ExecForeignUpdate_function ExecForeignUpdate;
+	EndForeignModify_function EndForeignModify;
 } FdwRoutine;
 
 
diff --git a/src/include/foreign/foreign.h b/src/include/foreign/foreign.h
index 5bd6ae6..749f9a6 100644
--- a/src/include/foreign/foreign.h
+++ b/src/include/foreign/foreign.h
@@ -14,6 +14,8 @@
 #define FOREIGN_H
 
 #include "nodes/parsenodes.h"
+#include "nodes/plannodes.h"
+#include "nodes/relation.h"
 
 
 /* Helper for obtaining username for user mapping */
@@ -81,4 +83,8 @@ extern List *GetForeignColumnOptions(Oid relid, AttrNumber attnum);
 extern Oid	get_foreign_data_wrapper_oid(const char *fdwname, bool missing_ok);
 extern Oid	get_foreign_server_oid(const char *servername, bool missing_ok);
 
+extern AttrNumber get_pseudo_rowid_column(RelOptInfo *baserel,
+										  List *targetList);
+extern ForeignScan *lookup_foreign_scan_plan(Plan *subplan,
+											 Index rtindex);
 #endif   /* FOREIGN_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 76e8cdb..97c2045 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -268,9 +268,11 @@ typedef struct ProjectionInfo
  *						attribute numbers of the "original" tuple and the
  *						attribute numbers of the "clean" tuple.
  *	  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
+ *	  jf_junkRowidNo:	not used by junkfilter code.  Can be used by caller
+ *						to remember the attno used to track a particular tuple
+ *						being updated or deleted.
  *						(execMain.c stores the "ctid" attno here).
+ *	  jf_junkRecordNo:	Also, the attno of whole-row reference.
  * ----------------
  */
 typedef struct JunkFilter
@@ -280,7 +282,8 @@ typedef struct JunkFilter
 	TupleDesc	jf_cleanTupType;
 	AttrNumber *jf_cleanMap;
 	TupleTableSlot *jf_resultSlot;
-	AttrNumber	jf_junkAttNo;
+	AttrNumber	jf_junkRowidNo;
+	AttrNumber	jf_junkRecordNo;
 } JunkFilter;
 
 /* ----------------
@@ -303,6 +306,8 @@ typedef struct JunkFilter
  *		ConstraintExprs			array of constraint-checking expr states
  *		junkFilter				for removing junk attributes from tuples
  *		projectReturning		for computing a RETURNING list
+ *		fdwroutine				FDW callbacks if foreign table
+ *		fdw_state				opaque state of FDW module, or NULL
  * ----------------
  */
 typedef struct ResultRelInfo
@@ -320,6 +325,8 @@ typedef struct ResultRelInfo
 	List	  **ri_ConstraintExprs;
 	JunkFilter *ri_junkFilter;
 	ProjectionInfo *ri_projectReturning;
+	struct FdwRoutine  *ri_fdwroutine;
+	void	   *ri_fdw_state;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 0b8b107..1fd907d 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -175,6 +175,7 @@ typedef struct ModifyTable
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
 	int			epqParam;		/* ID of Param for EvalPlanQual re-eval */
+	List	   *fdwPrivList;	/* private fields for foreign tables */
 } ModifyTable;
 
 /* ----------------
@@ -478,6 +479,7 @@ typedef struct ForeignScan
 	List	   *fdw_exprs;		/* expressions that FDW may evaluate */
 	List	   *fdw_private;	/* private data for FDW */
 	bool		fsSystemCol;	/* true if any "system column" is needed */
+	bool		fsPseudoCol;	/* true if any "pseudo column" is needed */
 } ForeignScan;
 
 
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index bc68789..91b2983 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -133,7 +133,7 @@ extern Path *reparameterize_path(PlannerInfo *root, Path *path,
  */
 extern void setup_simple_rel_arrays(PlannerInfo *root);
 extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid,
-				 RelOptKind reloptkind);
+			   List *tlist, RelOptKind reloptkind);
 extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid);
 extern RelOptInfo *find_join_rel(PlannerInfo *root, Relids relids);
 extern RelOptInfo *build_join_rel(PlannerInfo *root,
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index 6abd92a..59f175d 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -26,7 +26,7 @@ extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook;
 
 
 extern void get_relation_info(PlannerInfo *root, Oid relationObjectId,
-				  bool inhparent, RelOptInfo *rel);
+				  bool inhparent, List *tlist, RelOptInfo *rel);
 
 extern void estimate_rel_size(Relation rel, int32 *attr_widths,
 				  BlockNumber *pages, double *tuples, double *allvisfrac);
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 2f9fcd5..aa6f504 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);
@@ -90,7 +91,8 @@ extern bool is_projection_capable_plan(Plan *plan);
 extern int	from_collapse_limit;
 extern int	join_collapse_limit;
 
-extern void add_base_rels_to_query(PlannerInfo *root, Node *jtnode);
+extern void add_base_rels_to_query(PlannerInfo *root, List *tlist,
+								   Node *jtnode);
 extern void build_base_rel_tlists(PlannerInfo *root, List *final_tlist);
 extern void add_vars_to_targetlist(PlannerInfo *root, List *vars,
 					   Relids where_needed, bool create_new_ph);
#50Daniel Farina
daniel@heroku.com
In reply to: Kohei KaiGai (#49)
Re: [v9.3] writable foreign tables

On Tue, Jan 29, 2013 at 1:19 AM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

I noticed the v10 patch cannot be applied on the latest master branch
cleanly. The attached ones were rebased.

Hello,

I'm just getting started looking at this, but notice that the second
patch relies on contrib/postgres_fdw to apply, but it's not clear to
me where to get the correct version of that. One way to solve this
would be to tell me where to get a version of that, and another that
may work well would be to relay a Git repository with postgres_fdw and
the writable fdw changes accumulated.

I poked around on git.postgresql.org and github and wasn't able to
find a recent pushed copy of this.

Anyway, I'm looking at the first patch, which applies neatly. Thanks.

--
fdr

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#51Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Daniel Farina (#50)
Re: [v9.3] writable foreign tables

2013/2/1 Daniel Farina <daniel@heroku.com>:

On Tue, Jan 29, 2013 at 1:19 AM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

I noticed the v10 patch cannot be applied on the latest master branch
cleanly. The attached ones were rebased.

Hello,

I'm just getting started looking at this, but notice that the second
patch relies on contrib/postgres_fdw to apply, but it's not clear to
me where to get the correct version of that. One way to solve this
would be to tell me where to get a version of that, and another that
may work well would be to relay a Git repository with postgres_fdw and
the writable fdw changes accumulated.

I poked around on git.postgresql.org and github and wasn't able to
find a recent pushed copy of this.

Anyway, I'm looking at the first patch, which applies neatly. Thanks.

Thanks for your reviewing.

The second part assumes the latest (v5) postgres_fdw patch being
applied. It has been in status of ready-for-committer since last CF,
and nothing was changed.
https://commitfest.postgresql.org/action/patch_view?id=940

If I oversight nothing, it is the latest version I reviewed before.
Is there somebody who can volunteer to review his patch and commit it?

Hanada-san, if you have some updates, please share it with us.

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#52Daniel Farina
daniel@heroku.com
In reply to: Daniel Farina (#50)
1 attachment(s)
Re: [v9.3] writable foreign tables

On Fri, Feb 1, 2013 at 2:22 AM, Daniel Farina <daniel@heroku.com> wrote:

On Tue, Jan 29, 2013 at 1:19 AM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

I noticed the v10 patch cannot be applied on the latest master branch
cleanly. The attached ones were rebased.

Anyway, I'm looking at the first patch, which applies neatly. Thanks.

Hello,

My review is incomplete, but to the benefit of pipelining I wanted to
ask a couple of things and submit some changes for your consideration
while continuing to look at this.

So far, I've been trying to understand in very close detail how your
changes affect non-FDW related paths first, before delving into the
actual writable FDW functionality. There's not that much in this
category, but there's one thing that gave me pause: the fact that the
target list (by convention, tlist) has to be pushed down from
planmain.c/query_planner all the way to a
fdwroutine->GetForeignRelWidth callsite in plancat.c/get_relation_info
(so even in the end, it is just pushed down -- never inspected or
modified). In relative terms, it's a significant widening of
currently fairly short parameter lists in these procedures:

add_base_rels_to_query(PlannerInfo *root, List *tlist, Node *jtnode)
build_simple_rel(PlannerInfo *root, int relid, List *tlist, RelOptKind
reloptkind)
get_relation_info(PlannerInfo *root, Oid relationObjectId, bool
inhparent, List *tlist, RelOptInfo *rel)

In the case of all the other parameters except tlist, each is more
intimately related with the inner workings and mechanisms of the
procedure. I wonder if there's a nice way that can train the reader's
eye that the tlist is simply pushed down rather than actively managed
at each level. However, I can't immediately produce a way to both
achieve that that doesn't seem overwrought. Perhaps a comment will
suffice.

Related to this, I found that make_modifytable grew a dependency on
PlannerInfo *root, and it seemed like a bunch of the arguments are
functionally related to that, so the attached patch that should be
able to be applied to yours tries to utilize that as often as
possible. The weirdest thing in there is how make_modifytable has
been taught to call SS_assign_special_param, which has a side effect
on the PlannerInfo, but there exist exactly zero cases where one
*doesn't* want to do that prior to the (exactly two) call sites to
make_modifytable, so I pushed it in. The second weirdest thing is
pushing in the handling of rowMarks (e.g. SELECT FOR UPDATE et al)

Let me know as to your thoughts, otherwise I'll keep moving on...

--
fdr

Attachments:

wfdw-rely-on-plannerinfo.patchapplication/octet-stream; name=wfdw-rely-on-plannerinfo.patchDownload
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
***************
*** 4715,4724 **** make_result(PlannerInfo *root,
   */
  ModifyTable *
  make_modifytable(PlannerInfo *root,
- 				 CmdType operation, bool canSetTag,
  				 List *resultRelations,
! 				 List *subplans, List *returningLists,
! 				 List *rowMarks, int epqParam)
  {
  	ModifyTable *node = makeNode(ModifyTable);
  	Plan	   *plan = &node->plan;
--- 4715,4722 ----
   */
  ModifyTable *
  make_modifytable(PlannerInfo *root,
  				 List *resultRelations,
! 				 List *subplans, List *returningLists)
  {
  	ModifyTable *node = makeNode(ModifyTable);
  	Plan	   *plan = &node->plan;
***************
*** 4726,4731 **** make_modifytable(PlannerInfo *root,
--- 4724,4743 ----
  	ListCell   *subnode;
  	ListCell   *resultRel;
  	List	   *fdw_priv_list = NIL;
+ 	CmdType		operation = root->parse->commandType;
+ 	bool		canSetTag = root->parse->canSetTag;
+ 	List	   *rowMarks;
+ 	int			epqParam = SS_assign_special_param(root);
+ 
+ 	/*
+ 	 * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node will
+ 	 * have dealt with fetching non-locked marked rows, else we need
+ 	 * to have ModifyTable do that.
+ 	 */
+ 	if (root->parse->rowMarks)
+ 		rowMarks = NIL;
+ 	else
+ 		rowMarks = root->rowMarks;
  
  	Assert(list_length(resultRelations) == list_length(subplans));
  	Assert(returningLists == NIL ||
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
***************
*** 551,557 **** subquery_planner(PlannerGlobal *glob, Query *parse,
  		if (parse->commandType != CMD_SELECT)
  		{
  			List	   *returningLists;
- 			List	   *rowMarks;
  
  			/*
  			 * Set up the RETURNING list-of-lists, if needed.
--- 551,556 ----
***************
*** 561,584 **** subquery_planner(PlannerGlobal *glob, Query *parse,
  			else
  				returningLists = NIL;
  
! 			/*
! 			 * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node will
! 			 * have dealt with fetching non-locked marked rows, else we need
! 			 * to have ModifyTable do that.
! 			 */
! 			if (parse->rowMarks)
! 				rowMarks = NIL;
! 			else
! 				rowMarks = root->rowMarks;
! 
  			plan = (Plan *) make_modifytable(root,
- 											 parse->commandType,
- 											 parse->canSetTag,
  									   list_make1_int(parse->resultRelation),
  											 list_make1(plan),
! 											 returningLists,
! 											 rowMarks,
! 											 SS_assign_special_param(root));
  		}
  	}
  
--- 560,571 ----
  			else
  				returningLists = NIL;
  
! 			Assert(parse->commandType == root->parse->commandType);
! 			Assert(parse->canSetTag == root->parse->canSetTag);
  			plan = (Plan *) make_modifytable(root,
  									   list_make1_int(parse->resultRelation),
  											 list_make1(plan),
! 											 returningLists);
  		}
  	}
  
***************
*** 762,768 **** inheritance_planner(PlannerInfo *root)
  	List	   *subplans = NIL;
  	List	   *resultRelations = NIL;
  	List	   *returningLists = NIL;
- 	List	   *rowMarks;
  	ListCell   *lc;
  
  	/*
--- 749,754 ----
***************
*** 954,978 **** inheritance_planner(PlannerInfo *root)
  	root->simple_rel_array_size = save_rel_array_size;
  	root->simple_rel_array = save_rel_array;
  
- 	/*
- 	 * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node will have
- 	 * dealt with fetching non-locked marked rows, else we need to have
- 	 * ModifyTable do that.
- 	 */
- 	if (parse->rowMarks)
- 		rowMarks = NIL;
- 	else
- 		rowMarks = root->rowMarks;
- 
  	/* And last, tack on a ModifyTable node to do the UPDATE/DELETE work */
  	return (Plan *) make_modifytable(root,
- 									 parse->commandType,
- 									 parse->canSetTag,
  									 resultRelations,
  									 subplans,
! 									 returningLists,
! 									 rowMarks,
! 									 SS_assign_special_param(root));
  }
  
  /*--------------------
--- 940,952 ----
  	root->simple_rel_array_size = save_rel_array_size;
  	root->simple_rel_array = save_rel_array;
  
  	/* And last, tack on a ModifyTable node to do the UPDATE/DELETE work */
+ 	Assert(parse->commandType == root->parse->commandType);
+ 	Assert(parse->canSetTag == root->parse->canSetTag);
  	return (Plan *) make_modifytable(root,
  									 resultRelations,
  									 subplans,
! 									 returningLists);
  }
  
  /*--------------------
*** a/src/include/optimizer/planmain.h
--- b/src/include/optimizer/planmain.h
***************
*** 80,88 **** extern SetOp *make_setop(SetOpCmd cmd, SetOpStrategy strategy, Plan *lefttree,
  extern Result *make_result(PlannerInfo *root, List *tlist,
  			Node *resconstantqual, Plan *subplan);
  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);
  
  /*
--- 80,86 ----
  extern Result *make_result(PlannerInfo *root, List *tlist,
  			Node *resconstantqual, Plan *subplan);
  extern ModifyTable *make_modifytable(PlannerInfo *root,
! 				 List *resultRelations, List *subplans, List *returningLists);
  extern bool is_projection_capable_plan(Plan *plan);
  
  /*
#53Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Daniel Farina (#52)
2 attachment(s)
Re: [v9.3] writable foreign tables

2013/2/5 Daniel Farina <daniel@heroku.com>:

On Fri, Feb 1, 2013 at 2:22 AM, Daniel Farina <daniel@heroku.com> wrote:

On Tue, Jan 29, 2013 at 1:19 AM, Kohei KaiGai <kaigai@kaigai.gr.jp> wrote:

I noticed the v10 patch cannot be applied on the latest master branch
cleanly. The attached ones were rebased.

Anyway, I'm looking at the first patch, which applies neatly. Thanks.

Hello,

My review is incomplete, but to the benefit of pipelining I wanted to
ask a couple of things and submit some changes for your consideration
while continuing to look at this.

So far, I've been trying to understand in very close detail how your
changes affect non-FDW related paths first, before delving into the
actual writable FDW functionality. There's not that much in this
category, but there's one thing that gave me pause: the fact that the
target list (by convention, tlist) has to be pushed down from
planmain.c/query_planner all the way to a
fdwroutine->GetForeignRelWidth callsite in plancat.c/get_relation_info
(so even in the end, it is just pushed down -- never inspected or
modified). In relative terms, it's a significant widening of
currently fairly short parameter lists in these procedures:

add_base_rels_to_query(PlannerInfo *root, List *tlist, Node *jtnode)
build_simple_rel(PlannerInfo *root, int relid, List *tlist, RelOptKind
reloptkind)
get_relation_info(PlannerInfo *root, Oid relationObjectId, bool
inhparent, List *tlist, RelOptInfo *rel)

In the case of all the other parameters except tlist, each is more
intimately related with the inner workings and mechanisms of the
procedure. I wonder if there's a nice way that can train the reader's
eye that the tlist is simply pushed down rather than actively managed
at each level. However, I can't immediately produce a way to both
achieve that that doesn't seem overwrought. Perhaps a comment will
suffice.

Thanks for your checks.

add_base_rels_to_query() originally has two arguments; PlannerInfo
and Node, but these are wide-spreading in use. I don't think it is a good
idea to add a special field to reduce number of arguments only.
Instead, I tried to add a short comment on top of add_base_rels_to_query()
as follows.

* XXX - Also note that tlist needs to be pushed down into deeper level,
* for construction of RelOptInfo relevant to foreign-tables with pseudo-
* columns.
*/

I'm not 100% sure whether it is a good comment here, because internal
will be revised patch-by-patch. So, if future version also modifies argument
list here, this comment may talks a lie.

Related to this, I found that make_modifytable grew a dependency on
PlannerInfo *root, and it seemed like a bunch of the arguments are
functionally related to that, so the attached patch that should be
able to be applied to yours tries to utilize that as often as
possible. The weirdest thing in there is how make_modifytable has
been taught to call SS_assign_special_param, which has a side effect
on the PlannerInfo, but there exist exactly zero cases where one
*doesn't* want to do that prior to the (exactly two) call sites to
make_modifytable, so I pushed it in. The second weirdest thing is
pushing in the handling of rowMarks (e.g. SELECT FOR UPDATE et al)

Good suggestion. I added the PlannerInfo *root with spinal-reflexes.
Indeed, I'd like to agree with your optimization.

The attached patch adds Daniel's reworks on make_modifytable
invocation, and add a short comment on add_base_rels_to_query().
Rest of portion has not been changed from the previous version.

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

Attachments:

pgsql-v9.3-writable-fdw-poc.v12.part-2.patchapplication/octet-stream; name=pgsql-v9.3-writable-fdw-poc.v12.part-2.patchDownload
 contrib/postgres_fdw/connection.c              |   70 +-
 contrib/postgres_fdw/connection.h              |    8 +-
 contrib/postgres_fdw/deparse.c                 |  158 ++-
 contrib/postgres_fdw/expected/postgres_fdw.out | 1232 +++++++++++++++++++++++-
 contrib/postgres_fdw/postgres_fdw.c            |  504 +++++++++-
 contrib/postgres_fdw/postgres_fdw.h            |    8 +-
 contrib/postgres_fdw/sql/postgres_fdw.sql      |   31 +
 7 files changed, 1978 insertions(+), 33 deletions(-)

diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index eab8b87..9ca7fbf 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -43,7 +43,7 @@ typedef struct ConnCacheEntry
 	Oid				serverid;	/* oid of foreign server */
 	Oid				userid;		/* oid of local user */
 
-	bool			use_tx;		/* true when using remote transaction */
+	int				conntx;		/* one of PGSQL_FDW_CONNTX_* */
 	int				refs;		/* reference counter */
 	PGconn		   *conn;		/* foreign server connection */
 } ConnCacheEntry;
@@ -65,6 +65,8 @@ cleanup_connection(ResourceReleasePhase phase,
 static PGconn *connect_pg_server(ForeignServer *server, UserMapping *user);
 static void begin_remote_tx(PGconn *conn);
 static void abort_remote_tx(PGconn *conn);
+static void commit_remote_tx(PGconn *conn);
+static void deallocate_remote_prepare(PGconn *conn);
 
 /*
  * Get a PGconn which can be used to execute foreign query on the remote
@@ -80,7 +82,7 @@ static void abort_remote_tx(PGconn *conn);
  * FDW object to invalidate already established connections.
  */
 PGconn *
-GetConnection(ForeignServer *server, UserMapping *user, bool use_tx)
+GetConnection(ForeignServer *server, UserMapping *user, int conntx)
 {
 	bool			found;
 	ConnCacheEntry *entry;
@@ -126,7 +128,7 @@ GetConnection(ForeignServer *server, UserMapping *user, bool use_tx)
 	entry = hash_search(ConnectionHash, &key, HASH_ENTER, &found);
 	if (!found)
 	{
-		entry->use_tx = false;
+		entry->conntx = PGSQL_FDW_CONNTX_NONE;
 		entry->refs = 0;
 		entry->conn = NULL;
 	}
@@ -162,7 +164,7 @@ GetConnection(ForeignServer *server, UserMapping *user, bool use_tx)
 		{
 			/* Clear connection cache entry on error case. */
 			PQfinish(entry->conn);
-			entry->use_tx = false;
+			entry->conntx = PGSQL_FDW_CONNTX_NONE;
 			entry->refs = 0;
 			entry->conn = NULL;
 			PG_RE_THROW();
@@ -182,10 +184,11 @@ GetConnection(ForeignServer *server, UserMapping *user, bool use_tx)
 	 * are in.  We need to remember whether this connection uses remote
 	 * transaction to abort it when this connection is released completely.
 	 */
-	if (use_tx && !entry->use_tx)
+	if (conntx > entry->conntx)
 	{
-		begin_remote_tx(entry->conn);
-		entry->use_tx = use_tx;
+		if (entry->conntx == PGSQL_FDW_CONNTX_NONE)
+			begin_remote_tx(entry->conn);
+		entry->conntx = conntx;
 	}
 
 	return entry->conn;
@@ -355,12 +358,45 @@ abort_remote_tx(PGconn *conn)
 	PQclear(res);
 }
 
+static void
+commit_remote_tx(PGconn *conn)
+{
+	PGresult	   *res;
+
+	elog(DEBUG3, "committing remote transaction");
+
+	res = PQexec(conn, "COMMIT TRANSACTION");
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		PQclear(res);
+		elog(ERROR, "could not commit transaction: %s", PQerrorMessage(conn));
+	}
+	PQclear(res);
+}
+
+static void
+deallocate_remote_prepare(PGconn *conn)
+{
+	PGresult	   *res;
+
+	elog(DEBUG3, "deallocating remote prepares");
+
+	res = PQexec(conn, "DEALLOCATE PREPARE ALL");
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		PQclear(res);
+		elog(ERROR, "could not deallocate prepared statement: %s",
+			 PQerrorMessage(conn));
+	}
+	PQclear(res);
+}
+
 /*
  * Mark the connection as "unused", and close it if the caller was the last
  * user of the connection.
  */
 void
-ReleaseConnection(PGconn *conn)
+ReleaseConnection(PGconn *conn, bool is_abort)
 {
 	HASH_SEQ_STATUS		scan;
 	ConnCacheEntry	   *entry;
@@ -412,7 +448,7 @@ ReleaseConnection(PGconn *conn)
 			 PQtransactionStatus(conn) == PQTRANS_INERROR ? "INERROR" :
 			 "UNKNOWN");
 		PQfinish(conn);
-		entry->use_tx = false;
+		entry->conntx = PGSQL_FDW_CONNTX_NONE;
 		entry->refs = 0;
 		entry->conn = NULL;
 		return;
@@ -430,10 +466,16 @@ ReleaseConnection(PGconn *conn)
 	 * If this connection uses remote transaction and there is no user other
 	 * than the caller, abort the remote transaction and forget about it.
 	 */
-	if (entry->use_tx && entry->refs == 0)
+	if (entry->conntx > PGSQL_FDW_CONNTX_NONE && entry->refs == 0)
 	{
-		abort_remote_tx(conn);
-		entry->use_tx = false;
+		if (entry->conntx > PGSQL_FDW_CONNTX_READ_ONLY)
+			deallocate_remote_prepare(conn);
+		if (is_abort || entry->conntx == PGSQL_FDW_CONNTX_READ_ONLY)
+			abort_remote_tx(conn);
+		else
+			commit_remote_tx(conn);
+
+		entry->conntx = PGSQL_FDW_CONNTX_NONE;
 	}
 }
 
@@ -485,7 +527,7 @@ cleanup_connection(ResourceReleasePhase phase,
 		elog(DEBUG3, "discard postgres_fdw connection %p due to resowner cleanup",
 			 entry->conn);
 		PQfinish(entry->conn);
-		entry->use_tx = false;
+		entry->conntx = PGSQL_FDW_CONNTX_NONE;
 		entry->refs = 0;
 		entry->conn = NULL;
 	}
@@ -597,7 +639,7 @@ postgres_fdw_disconnect(PG_FUNCTION_ARGS)
 
 	/* Discard cached connection, and clear reference counter. */
 	PQfinish(entry->conn);
-	entry->use_tx = false;
+	entry->conntx = PGSQL_FDW_CONNTX_NONE;
 	entry->refs = 0;
 	entry->conn = NULL;
 
diff --git a/contrib/postgres_fdw/connection.h b/contrib/postgres_fdw/connection.h
index 4c9d850..f97cc8a 100644
--- a/contrib/postgres_fdw/connection.h
+++ b/contrib/postgres_fdw/connection.h
@@ -16,10 +16,14 @@
 #include "foreign/foreign.h"
 #include "libpq-fe.h"
 
+#define PGSQL_FDW_CONNTX_NONE			0
+#define PGSQL_FDW_CONNTX_READ_ONLY		1
+#define PGSQL_FDW_CONNTX_READ_WRITE		2
+
 /*
  * Connection management
  */
-PGconn *GetConnection(ForeignServer *server, UserMapping *user, bool use_tx);
-void ReleaseConnection(PGconn *conn);
+PGconn *GetConnection(ForeignServer *server, UserMapping *user, int conntx);
+void ReleaseConnection(PGconn *conn, bool is_abort);
 
 #endif /* CONNECTION_H */
diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c
index 69e6a3e..ce8c2e7 100644
--- a/contrib/postgres_fdw/deparse.c
+++ b/contrib/postgres_fdw/deparse.c
@@ -12,6 +12,7 @@
  */
 #include "postgres.h"
 
+#include "access/heapam.h"
 #include "access/htup_details.h"
 #include "access/transam.h"
 #include "catalog/pg_class.h"
@@ -86,9 +87,11 @@ void
 deparseSimpleSql(StringInfo buf,
 				 PlannerInfo *root,
 				 RelOptInfo *baserel,
-				 List *local_conds)
+				 List *local_conds,
+				 AttrNumber anum_rowid)
 {
 	RangeTblEntry *rte;
+	Relation	rel;
 	ListCell   *lc;
 	StringInfoData	foreign_relname;
 	bool		first;
@@ -125,6 +128,24 @@ deparseSimpleSql(StringInfo buf,
 	}
 
 	/*
+	 * XXX - When this foreign table is target relation and RETURNING
+	 * clause reference some column, we have to mark these columns as
+	 * in-use. It is needed to support DELETE command, because INSERT
+	 * and UPDATE implicitly add references to all the regular columns
+	 * on baserel->reltargetlist.
+	 */
+	if (root->parse->resultRelation == baserel->relid &&
+		root->parse->returningList)
+	{
+		List   *attrs;
+
+		attrs = pull_var_clause((Node *) root->parse->returningList,
+								PVC_RECURSE_AGGREGATES,
+                                PVC_RECURSE_PLACEHOLDERS);
+		attr_used = list_union(attr_used, attrs);
+	}
+
+	/*
 	 * deparse SELECT clause
 	 *
 	 * List attributes which are in either target list or local restriction.
@@ -136,9 +157,10 @@ deparseSimpleSql(StringInfo buf,
 	 */
 	appendStringInfo(buf, "SELECT ");
 	rte = root->simple_rte_array[baserel->relid];
+	rel = heap_open(rte->relid, NoLock);
 	attr_used = list_union(attr_used, baserel->reltargetlist);
 	first = true;
-	for (attr = 1; attr <= baserel->max_attr; attr++)
+	for (attr = 1; attr <= RelationGetNumberOfAttributes(rel); attr++)
 	{
 		Var		   *var = NULL;
 		ListCell   *lc;
@@ -167,6 +189,10 @@ deparseSimpleSql(StringInfo buf,
 		else
 			appendStringInfo(buf, "NULL");
 	}
+	if (anum_rowid != InvalidAttrNumber)
+		appendStringInfo(buf, "%sctid", (first ? "" : ", "));
+
+	heap_close(rel, NoLock);
 	appendStringInfoChar(buf, ' ');
 
 	/*
@@ -283,6 +309,134 @@ deparseAnalyzeSql(StringInfo buf, Relation rel)
 }
 
 /*
+ * deparse RETURNING clause of INSERT/UPDATE/DELETE
+ */
+static void
+deparseReturningSql(StringInfo buf, PlannerInfo *root, Index rtindex,
+					Relation frel)
+{
+	AttrNumber	i, nattrs = RelationGetNumberOfAttributes(frel);
+
+	appendStringInfo(buf, " RETURNING ");
+	for (i=0; i < nattrs; i++)
+	{
+		Form_pg_attribute attr = RelationGetDescr(frel)->attrs[i];
+
+		if (i > 0)
+			appendStringInfo(buf, ",");
+
+		if (attr->attisdropped)
+			appendStringInfo(buf, "null");
+		else
+		{
+			Var		var;
+
+			var.varno = rtindex;
+			var.varattno = attr->attnum;
+			deparseVar(buf, &var, root);
+		}
+	}
+}
+
+/*
+ * deparse remote INSERT statement
+ */
+void
+deparseInsertSql(StringInfo buf, PlannerInfo *root, Index rtindex,
+				 List *targetAttrs, bool has_returning)
+{
+	RangeTblEntry  *rte = root->simple_rte_array[rtindex];
+	Relation		frel = heap_open(rte->relid, NoLock);
+	ListCell	   *lc;
+	AttrNumber		pindex = 1;
+
+	appendStringInfo(buf, "INSERT INTO ");
+	deparseRelation(buf, rte);
+	appendStringInfo(buf, "(");
+
+	foreach (lc, targetAttrs)
+	{
+		Var		var;
+		Form_pg_attribute	attr
+			= RelationGetDescr(frel)->attrs[lfirst_int(lc) - 1];
+
+		Assert(!attr->attisdropped);
+		if (lc != list_head(targetAttrs))
+			appendStringInfo(buf, ",");
+
+		var.varno = rtindex;
+		var.varattno = attr->attnum;
+		deparseVar(buf, &var, root);
+	}
+	appendStringInfo(buf, ") VALUES (");
+
+	foreach (lc, targetAttrs)
+	{
+		appendStringInfo(buf, "%s$%d", (pindex == 1 ? "" : ","), pindex);
+		pindex++;
+	}
+	appendStringInfo(buf, ")");
+
+	if (has_returning)
+		deparseReturningSql(buf, root, rtindex, frel);
+
+	heap_close(frel, NoLock);
+}
+
+/*
+ * deparse remote UPDATE statement
+ */
+void
+deparseUpdateSql(StringInfo buf, PlannerInfo *root, Index rtindex,
+				 List *targetAttrs, bool has_returning)
+{
+	RangeTblEntry  *rte = root->simple_rte_array[rtindex];
+	Relation		frel = heap_open(rte->relid, NoLock);
+	ListCell	   *lc;
+	AttrNumber		pindex = 2;
+
+	appendStringInfo(buf, "UPDATE ");
+	deparseRelation(buf, rte);
+	appendStringInfo(buf, " SET ");
+
+	foreach (lc, targetAttrs)
+	{
+		Var		var;
+		Form_pg_attribute	attr
+			= RelationGetDescr(frel)->attrs[lfirst_int(lc) - 1];
+
+		Assert(!attr->attisdropped);
+
+		if (lc != list_head(targetAttrs))
+			appendStringInfo(buf, ",");
+
+		var.varno = rtindex;
+		var.varattno = attr->attnum;
+		deparseVar(buf, &var, root);
+		appendStringInfo(buf, "=$%d", pindex++);
+	}
+	appendStringInfo(buf, " WHERE ctid=$1");
+
+	if (has_returning)
+		deparseReturningSql(buf, root, rtindex, frel);
+
+	heap_close(frel, NoLock);
+}
+
+/*
+ * deparse remote DELETE statement
+ */
+void
+deparseDeleteSql(StringInfo buf, PlannerInfo *root, Index rtindex)
+{
+	RangeTblEntry  *rte = root->simple_rte_array[rtindex];
+
+	appendStringInfo(buf, "DELETE FROM ");
+	deparseRelation(buf, rte);
+	appendStringInfo(buf, " WHERE ctid = $1");
+}
+
+/*
  * Deparse given expression into buf.  Actual string operation is delegated to
  * node-type-specific functions.
  *
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index f81c727..c7607c5 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -736,15 +736,1245 @@ SELECT srvname FROM postgres_fdw_connections;
 (0 rows)
 
 -- ===================================================================
+-- test for writable foreign table stuff (PoC stage now)
+-- ===================================================================
+EXPLAIN(verbose) INSERT INTO ft2 (c1,c2,c3) (SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20);
+                                                                                                            QUERY PLAN                                                                                                             
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Insert on public.ft2  (cost=100.00..100.03 rows=1 width=40)
+   ->  Subquery Scan on "*SELECT*"  (cost=100.00..100.03 rows=1 width=40)
+         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  (cost=100.00..100.02 rows=1 width=40)
+               Output: ((ft2_1.c1 + 1000)), ((ft2_1.c2 + 100)), ((ft2_1.c3 || ft2_1.c3))
+               ->  Foreign Scan on public.ft2 ft2_1  (cost=100.00..100.02 rows=1 width=40)
+                     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"
+(8 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) 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  (cost=200.00..200.04 rows=1 width=344)
+   ->  Nested Loop  (cost=200.00..200.04 rows=1 width=344)
+         Output: NULL::integer, ft2.c1, (ft2.c2 + 500), (ft2.c3 || '_update9'::text), ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid, ft2.*, ft1.*
+         Join Filter: (ft2.c2 = ft1.c1)
+         ->  Foreign Scan on public.ft2  (cost=100.00..100.01 rows=1 width=232)
+               Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid, ft2.*
+               Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" FOR UPDATE
+         ->  Foreign Scan on public.ft1  (cost=100.00..100.01 rows=1 width=116)
+               Output: ft1.*, ft1.c1
+               Remote SQL: SELECT "C 1", NULL, NULL, NULL, NULL, NULL, NULL, NULL FROM "S 1"."T 1" WHERE ((("C 1" OPERATOR(pg_catalog.%) 10) OPERATOR(pg_catalog.=) 9))
+(10 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) DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2;
+                                                                               QUERY PLAN                                                                               
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Delete on public.ft2  (cost=200.00..200.03 rows=1 width=256)
+   ->  Nested Loop  (cost=200.00..200.03 rows=1 width=256)
+         Output: ft2.ctid, ft2.*, ft1.*
+         Join Filter: (ft2.c2 = ft1.c1)
+         ->  Foreign Scan on public.ft2  (cost=100.00..100.01 rows=1 width=148)
+               Output: ft2.ctid, ft2.*, ft2.c2
+               Remote SQL: SELECT NULL, c2, NULL, NULL, NULL, NULL, NULL, NULL, ctid FROM "S 1"."T 1" FOR UPDATE
+         ->  Foreign Scan on public.ft1  (cost=100.00..100.01 rows=1 width=116)
+               Output: ft1.*, ft1.c1
+               Remote SQL: SELECT "C 1", NULL, NULL, NULL, NULL, NULL, NULL, NULL FROM "S 1"."T 1" WHERE ((("C 1" OPERATOR(pg_catalog.%) 10) OPERATOR(pg_catalog.=) 2))
+(10 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)
+
+-- In case of remote table has before-row trigger or default with returning
+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)
+
+-- ===================================================================
 -- cleanup
 -- ===================================================================
 DROP OPERATOR === (int, int) CASCADE;
 DROP OPERATOR !== (int, int) CASCADE;
 DROP FUNCTION postgres_fdw_abs(int);
 DROP SCHEMA "S 1" CASCADE;
-NOTICE:  drop cascades to 2 other objects
+NOTICE:  drop cascades to 3 other objects
 DETAIL:  drop cascades to table "S 1"."T 1"
 drop cascades to table "S 1"."T 2"
+drop cascades to function "S 1".f_brtrig()
 DROP TYPE user_enum CASCADE;
 NOTICE:  drop cascades to 2 other objects
 DETAIL:  drop cascades to foreign table ft2 column c8
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 6b870ab..420bb4d 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -14,6 +14,7 @@
 #include "fmgr.h"
 
 #include "access/htup_details.h"
+#include "access/sysattr.h"
 #include "catalog/pg_foreign_server.h"
 #include "catalog/pg_foreign_table.h"
 #include "catalog/pg_type.h"
@@ -27,6 +28,7 @@
 #include "optimizer/pathnode.h"
 #include "optimizer/planmain.h"
 #include "optimizer/restrictinfo.h"
+#include "parser/parsetree.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -59,6 +61,7 @@ typedef struct PostgresFdwPlanState {
 	List		   *param_conds;
 	List		   *local_conds;
 	int				width;			/* obtained by remote EXPLAIN */
+	AttrNumber		anum_rowid;
 
 	/* Cached catalog information. */
 	ForeignTable   *table;
@@ -150,6 +153,24 @@ typedef struct PostgresAnalyzeState
 } PostgresAnalyzeState;
 
 /*
+ * Describes a state of modify request for a foreign table
+ */
+typedef struct PostgresFdwModifyState
+{
+	PGconn	   *conn;
+	char	   *query;
+	bool		has_returning;
+	List	   *target_attrs;
+	char	   *p_name;
+	int			p_nums;
+	Oid		   *p_types;
+	FmgrInfo   *p_flinfo;
+	Oid		   *r_ioparam;
+	FmgrInfo   *r_flinfo;
+	MemoryContext	es_query_cxt;
+} PostgresFdwModifyState;
+
+/*
  * SQL functions
  */
 extern Datum postgres_fdw_handler(PG_FUNCTION_ARGS);
@@ -158,6 +179,11 @@ PG_FUNCTION_INFO_V1(postgres_fdw_handler);
 /*
  * FDW callback routines
  */
+static AttrNumber postgresGetForeignRelWidth(PlannerInfo *root,
+											 RelOptInfo *baserel,
+											 Relation foreignrel,
+											 bool inhparent,
+											 List *targetList);
 static void postgresGetForeignRelSize(PlannerInfo *root,
 									  RelOptInfo *baserel,
 									  Oid foreigntableid);
@@ -179,6 +205,23 @@ static void postgresEndForeignScan(ForeignScanState *node);
 static bool postgresAnalyzeForeignTable(Relation relation,
 										AcquireSampleRowsFunc *func,
 										BlockNumber *totalpages);
+static List *postgresPlanForeignModify(PlannerInfo *root,
+									   ModifyTable *plan,
+									   Index resultRelation,
+									   Plan *subplan);
+static void postgresBeginForeignModify(ModifyTableState *mtstate,
+									   ResultRelInfo *resultRelInfo,
+									   List *fdw_private,
+									   Plan *subplan,
+									   int eflags);
+static TupleTableSlot *postgresExecForeignInsert(ResultRelInfo *rinfo,
+												 TupleTableSlot *slot);
+static bool postgresExecForeignDelete(ResultRelInfo *rinfo,
+									  const char *rowid);
+static TupleTableSlot * postgresExecForeignUpdate(ResultRelInfo *rinfo,
+												  const char *rowid,
+												  TupleTableSlot *slot);
+static void postgresEndForeignModify(ResultRelInfo *rinfo);
 
 /*
  * Helper functions
@@ -231,6 +274,7 @@ postgres_fdw_handler(PG_FUNCTION_ARGS)
 	FdwRoutine	*routine = makeNode(FdwRoutine);
 
 	/* Required handler functions. */
+	routine->GetForeignRelWidth = postgresGetForeignRelWidth;
 	routine->GetForeignRelSize = postgresGetForeignRelSize;
 	routine->GetForeignPaths = postgresGetForeignPaths;
 	routine->GetForeignPlan = postgresGetForeignPlan;
@@ -239,6 +283,12 @@ postgres_fdw_handler(PG_FUNCTION_ARGS)
 	routine->IterateForeignScan = postgresIterateForeignScan;
 	routine->ReScanForeignScan = postgresReScanForeignScan;
 	routine->EndForeignScan = postgresEndForeignScan;
+	routine->PlanForeignModify = postgresPlanForeignModify;
+	routine->BeginForeignModify = postgresBeginForeignModify;
+	routine->ExecForeignInsert = postgresExecForeignInsert;
+	routine->ExecForeignDelete = postgresExecForeignDelete;
+	routine->ExecForeignUpdate = postgresExecForeignUpdate;
+	routine->EndForeignModify = postgresEndForeignModify;
 
 	/* Optional handler functions. */
 	routine->AnalyzeForeignTable = postgresAnalyzeForeignTable;
@@ -247,6 +297,34 @@ postgres_fdw_handler(PG_FUNCTION_ARGS)
 }
 
 /*
+ * postgresGetForeignRelWidth
+ *		Informs how many columns (including pseudo ones) are needed.
+ */
+static AttrNumber
+postgresGetForeignRelWidth(PlannerInfo *root,
+						   RelOptInfo *baserel,
+						   Relation foreignrel,
+						   bool inhparent,
+						   List *targetList)
+{
+	PostgresFdwPlanState *fpstate = palloc0(sizeof(PostgresFdwPlanState));
+
+	baserel->fdw_private = fpstate;
+
+	/* does rowid pseudo-column is required? */
+	fpstate->anum_rowid = get_pseudo_rowid_column(baserel, targetList);
+	if (fpstate->anum_rowid != InvalidAttrNumber)
+	{
+		RangeTblEntry *rte = rt_fetch(baserel->relid,
+									  root->parse->rtable);
+		rte->eref->colnames = lappend(rte->eref->colnames,
+									  makeString("ctid"));
+		return fpstate->anum_rowid;
+	}
+	return RelationGetNumberOfAttributes(foreignrel);
+}
+
+/*
  * postgresGetForeignRelSize
  *		Estimate # of rows and width of the result of the scan
  *
@@ -283,7 +361,7 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	 * We use PostgresFdwPlanState to pass various information to subsequent
 	 * functions.
 	 */
-	fpstate = palloc0(sizeof(PostgresFdwPlanState));
+	fpstate = baserel->fdw_private;
 	initStringInfo(&fpstate->sql);
 	sql = &fpstate->sql;
 
@@ -320,10 +398,9 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	 */
 	classifyConditions(root, baserel, &remote_conds, &param_conds,
 					   &local_conds);
-	deparseSimpleSql(sql, root, baserel, local_conds);
+	deparseSimpleSql(sql, root, baserel, local_conds, fpstate->anum_rowid);
 	if (list_length(remote_conds) > 0)
 		appendWhereClause(sql, true, remote_conds, root);
-	elog(DEBUG3, "Query SQL: %s", sql->data);
 
 	/*
 	 * If the table or the server is configured to use remote EXPLAIN, connect
@@ -337,10 +414,10 @@ postgresGetForeignRelSize(PlannerInfo *root,
 		PGconn		   *conn;
 
 		user = GetUserMapping(GetOuterUserId(), server->serverid);
-		conn = GetConnection(server, user, false);
+		conn = GetConnection(server, user, PGSQL_FDW_CONNTX_NONE);
 		get_remote_estimate(sql->data, conn, &rows, &width,
 							&startup_cost, &total_cost);
-		ReleaseConnection(conn);
+		ReleaseConnection(conn, false);
 
 		/*
 		 * Estimate selectivity of conditions which are not used in remote
@@ -391,7 +468,6 @@ postgresGetForeignRelSize(PlannerInfo *root,
 	fpstate->width = width;
 	fpstate->table = table;
 	fpstate->server = server;
-	baserel->fdw_private = (void *) fpstate;
 }
 
 /*
@@ -592,7 +668,7 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
 	table = GetForeignTable(relid);
 	server = GetForeignServer(table->serverid);
 	user = GetUserMapping(GetOuterUserId(), server->serverid);
-	conn = GetConnection(server, user, true);
+	conn = GetConnection(server, user, PGSQL_FDW_CONNTX_READ_ONLY);
 	festate->conn = conn;
 
 	/* Result will be filled in first Iterate call. */
@@ -724,7 +800,7 @@ postgresEndForeignScan(ForeignScanState *node)
 	 * end of the scan to make the lifespan of remote transaction same as the
 	 * local query.
 	 */
-	ReleaseConnection(festate->conn);
+	ReleaseConnection(festate->conn, false);
 	festate->conn = NULL;
 
 	/* Discard fetch results */
@@ -790,7 +866,7 @@ get_remote_estimate(const char *sql, PGconn *conn,
 		PQclear(res);
 
 		/* Release connection and let connection manager cleanup. */
-		ReleaseConnection(conn);
+		ReleaseConnection(conn, true);
 
 		PG_RE_THROW();
 	}
@@ -947,7 +1023,7 @@ execute_query(ForeignScanState *node)
 		PQclear(res);
 
 		/* Release connection and let connection manager cleanup. */
-		ReleaseConnection(conn);
+		ReleaseConnection(conn, true);
 
 		/* propagate error */
 		PG_RE_THROW();
@@ -1105,6 +1181,8 @@ postgres_fdw_error_callback(void *arg)
 
 	relname = get_rel_name(errpos->relid);
 	colname = get_attname(errpos->relid, errpos->cur_attno);
+	if (!colname)
+		colname = "pseudo-column";
 	errcontext("column %s of foreign table %s",
 			   quote_identifier(colname), quote_identifier(relname));
 }
@@ -1172,7 +1250,7 @@ postgresAcquireSampleRowsFunc(Relation relation, int elevel,
 	table = GetForeignTable(relation->rd_id);
 	server = GetForeignServer(table->serverid);
 	user = GetUserMapping(GetOuterUserId(), server->serverid);
-	conn = GetConnection(server, user, true);
+	conn = GetConnection(server, user, PGSQL_FDW_CONNTX_READ_ONLY);
 
 	/*
 	 * Acquire sample rows from the result set.
@@ -1239,13 +1317,13 @@ postgresAcquireSampleRowsFunc(Relation relation, int elevel,
 		PQclear(res);
 
 		/* Release connection and let connection manager cleanup. */
-		ReleaseConnection(conn);
+		ReleaseConnection(conn, true);
 
 		PG_RE_THROW();
 	}
 	PG_END_TRY();
 
-	ReleaseConnection(conn);
+	ReleaseConnection(conn, false);
 
 	/* We assume that we have no dead tuple. */
 	*totaldeadrows = 0.0;
@@ -1429,3 +1507,403 @@ analyze_row_processor(PGresult *res, PostgresAnalyzeState *astate, bool first)
 
 	return;
 }
+
+static List *
+postgresPlanForeignModify(PlannerInfo *root,
+						  ModifyTable *plan,
+						  Index resultRelation,
+						  Plan *subplan)
+{
+	CmdType			operation = plan->operation;
+	StringInfoData	sql;
+	List		   *targetAttrs = NIL;
+	bool			has_returning = (!!plan->returningLists);
+
+	initStringInfo(&sql);
+
+	/*
+	 * XXX - In case of UPDATE or DELETE commands are quite "simple",
+	 * we will be able to execute raw UPDATE or DELETE statement at
+	 * the stage of scan, instead of combination SELECT ... FOR UPDATE
+	 * and either of UPDATE or DELETE commands.
+	 * It should be an idea of optimization in the future version.
+	 *
+	 * XXX - FOR UPDATE should be appended on the remote query of scan
+	 * stage to avoid unexpected concurrent update on the target rows.
+	 */
+	if (operation == CMD_UPDATE || operation == CMD_DELETE)
+	{
+		ForeignScan	   *fscan;
+		Value		   *select_sql;
+
+		fscan = lookup_foreign_scan_plan(subplan, resultRelation);
+		if (!fscan)
+			elog(ERROR, "no underlying scan plan found in subplan tree");
+
+		select_sql = list_nth(fscan->fdw_private,
+							  FdwPrivateSelectSql);
+		appendStringInfo(&sql, "%s FOR UPDATE", strVal(select_sql));
+		strVal(select_sql) = pstrdup(sql.data);
+
+		resetStringInfo(&sql);
+	}
+
+	/*
+	 * XXX - In case of INSERT or UPDATE commands, it needs to list up
+	 * columns to be updated or inserted for performance optimization
+	 * and consistent behavior when DEFAULT is set on the remote table.
+	 */
+	if (operation == CMD_INSERT || operation == CMD_UPDATE)
+	{
+		RangeTblEntry  *rte = rt_fetch(resultRelation, root->parse->rtable);
+		Bitmapset	   *tmpset = bms_copy(rte->modifiedCols);
+		AttrNumber		col;
+
+		while ((col = bms_first_member(tmpset)) >= 0)
+		{
+			col += FirstLowInvalidHeapAttributeNumber;
+			if (col <= InvalidAttrNumber)
+				elog(ERROR, "system-column update is not supported");
+			targetAttrs = lappend_int(targetAttrs, col);
+		}
+	}
+
+	switch (operation)
+	{
+		case CMD_INSERT:
+			deparseInsertSql(&sql, root, resultRelation,
+							 targetAttrs, has_returning);
+			elog(DEBUG3, "Remote INSERT query: %s", sql.data);
+			break;
+		case CMD_UPDATE:
+			deparseUpdateSql(&sql, root, resultRelation,
+							 targetAttrs, has_returning);
+			elog(DEBUG3, "Remote UPDATE query: %s", sql.data);
+			break;
+		case CMD_DELETE:
+			deparseDeleteSql(&sql, root, resultRelation);
+			elog(DEBUG3, "Remote DELETE query: %s", sql.data);
+			break;
+		default:
+			elog(ERROR, "unexpected operation: %d", (int) operation);
+	}
+	return list_make3(makeString(sql.data),
+					  makeInteger(has_returning),
+					  targetAttrs);
+}
+
+static void
+postgresBeginForeignModify(ModifyTableState *mtstate,
+						   ResultRelInfo *resultRelInfo,
+						   List *fdw_private,
+						   Plan *subplan,
+						   int eflags)
+{
+	PostgresFdwModifyState *fmstate;
+	CmdType			operation = mtstate->operation;
+	Relation		frel = resultRelInfo->ri_RelationDesc;
+	AttrNumber		n_params;
+	ListCell	   *lc;
+	ForeignTable   *ftable;
+	ForeignServer  *fserver;
+	UserMapping	   *fuser;
+	Oid				typefnoid;
+	bool			isvarlena;
+
+	/*
+	 * Construct PostgresFdwExecutionState
+	 */
+	fmstate = palloc0(sizeof(PostgresFdwExecutionState));
+
+	ftable = GetForeignTable(RelationGetRelid(frel));
+	fserver = GetForeignServer(ftable->serverid);
+	fuser = GetUserMapping(GetOuterUserId(), fserver->serverid);
+
+	fmstate->query = strVal(linitial(fdw_private));
+	fmstate->has_returning = intVal(lsecond(fdw_private));
+	fmstate->target_attrs = lthird(fdw_private);
+	fmstate->conn = GetConnection(fserver, fuser,
+								  PGSQL_FDW_CONNTX_READ_WRITE);
+	n_params = list_length(fmstate->target_attrs) + 1;
+	fmstate->p_name = NULL;
+	fmstate->p_types = palloc0(sizeof(Oid) * n_params);
+	fmstate->p_flinfo = palloc0(sizeof(FmgrInfo) * n_params);
+
+	/* 1st parameter should be ctid on UPDATE or DELETE */
+	if (operation == CMD_UPDATE || operation == CMD_DELETE)
+	{
+		fmstate->p_types[fmstate->p_nums] = TIDOID;
+		getTypeOutputInfo(TIDOID, &typefnoid, &isvarlena);
+		fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
+		fmstate->p_nums++;
+	}
+	/* following parameters should be regular columns */
+	if (operation == CMD_UPDATE || operation == CMD_INSERT)
+	{
+		foreach (lc, fmstate->target_attrs)
+		{
+			Form_pg_attribute attr
+				= RelationGetDescr(frel)->attrs[lfirst_int(lc) - 1];
+
+			Assert(!attr->attisdropped);
+
+			fmstate->p_types[fmstate->p_nums] = attr->atttypid;
+			getTypeOutputInfo(attr->atttypid, &typefnoid, &isvarlena);
+			fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
+			fmstate->p_nums++;
+		}
+	}
+	Assert(fmstate->p_nums <= n_params);
+
+	/* input handlers for returning clause */
+	if (fmstate->has_returning)
+	{
+		AttrNumber	i, nattrs = RelationGetNumberOfAttributes(frel);
+
+		fmstate->r_ioparam = palloc0(sizeof(Oid) * nattrs);
+		fmstate->r_flinfo = palloc0(sizeof(FmgrInfo) * nattrs);
+		for (i=0; i < nattrs; i++)
+		{
+			Form_pg_attribute attr = RelationGetDescr(frel)->attrs[i];
+
+			if (attr->attisdropped)
+				continue;
+
+			getTypeInputInfo(attr->atttypid, &typefnoid,
+							 &fmstate->r_ioparam[i]);
+			fmgr_info(typefnoid, &fmstate->r_flinfo[i]);
+		}
+	}
+	fmstate->es_query_cxt = mtstate->ps.state->es_query_cxt;
+	resultRelInfo->ri_fdw_state = fmstate;
+}
+
+static void
+prepare_foreign_modify(PostgresFdwModifyState *fmstate)
+{
+	static int	prep_id = 1;
+	char		prep_name[NAMEDATALEN];
+	PGresult   *res;
+
+	snprintf(prep_name, sizeof(prep_name),
+			 "pgsql_fdw_prep_%08x", prep_id++);
+
+	res = PQprepare(fmstate->conn,
+					prep_name,
+					fmstate->query,
+					fmstate->p_nums,
+					fmstate->p_types);
+	if (!res || PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		PQclear(res);
+		elog(ERROR, "could not prepare statement (%s): %s",
+			 fmstate->query, PQerrorMessage(fmstate->conn));
+	}
+	PQclear(res);
+
+	fmstate->p_name = MemoryContextStrdup(fmstate->es_query_cxt, prep_name);
+}
+
+static int
+setup_exec_prepared(ResultRelInfo *resultRelInfo,
+					const char *rowid, TupleTableSlot *slot,
+					const char *p_values[], int p_lengths[])
+{
+	PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state;
+	int			pindex = 0;
+
+	/* 1st parameter should be ctid */
+	if (rowid)
+	{
+		p_values[pindex] = rowid;
+		p_lengths[pindex] = strlen(rowid) + 1;
+		pindex++;
+	}
+
+	/* following parameters are as TupleDesc */
+	if (slot != NULL)
+	{
+		TupleDesc	tupdesc = slot->tts_tupleDescriptor;
+		ListCell   *lc;
+
+		foreach (lc, fmstate->target_attrs)
+		{
+			Form_pg_attribute	attr = tupdesc->attrs[lfirst_int(lc) - 1];
+			Datum		value;
+			bool		isnull;
+
+			Assert(!attr->attisdropped);
+
+			value = slot_getattr(slot, attr->attnum, &isnull);
+			if (isnull)
+			{
+				p_values[pindex] = NULL;
+				p_lengths[pindex] = 0;
+			}
+			else
+			{
+				p_values[pindex] =
+					OutputFunctionCall(&fmstate->p_flinfo[pindex], value);
+				p_lengths[pindex] = strlen(p_values[pindex]) + 1;
+			}
+			pindex++;
+		}
+	}
+	return pindex;
+}
+
+static void
+store_returning_result(PostgresFdwModifyState *fmstate,
+					   TupleTableSlot *slot, PGresult *res)
+{
+	TupleDesc	tupdesc = slot->tts_tupleDescriptor;
+	AttrNumber	i, nattrs = tupdesc->natts;
+	Datum	   *values = alloca(sizeof(Datum) * nattrs);
+	bool	   *isnull = alloca(sizeof(bool) * nattrs);
+	HeapTuple	newtup;
+
+	memset(values, 0, sizeof(Datum) * nattrs);
+	memset(isnull, 0, sizeof(bool) * nattrs);
+
+	for (i=0; i < nattrs; i++)
+	{
+		Form_pg_attribute	attr = tupdesc->attrs[i];
+
+		if (attr->attisdropped || PQgetisnull(res, 0, i))
+			isnull[i] = true;
+		else
+		{
+			//elog(INFO, "col %d %s %d: value: %s fnoid: %u", i, NameStr(attr->attname), attr->attisdropped, PQgetvalue(res, 0, i), fmstate->r_flinfo[i].fn_oid);
+			values[i] = InputFunctionCall(&fmstate->r_flinfo[i],
+										  PQgetvalue(res, 0, i),
+										  fmstate->r_ioparam[i],
+										  attr->atttypmod);
+		}
+	}
+	newtup = heap_form_tuple(tupdesc, values, isnull);
+	ExecStoreTuple(newtup, slot, InvalidBuffer, false);
+}
+
+static TupleTableSlot *
+postgresExecForeignInsert(ResultRelInfo *resultRelInfo,
+						  TupleTableSlot *slot)
+{
+	PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state;
+	const char	  **p_values  = alloca(sizeof(char *) * fmstate->p_nums);
+	int			   *p_lengths = alloca(sizeof(int) * fmstate->p_nums);
+	AttrNumber		nattrs;
+	PGresult	   *res;
+	int				n_rows;
+
+	if (!fmstate->p_name)
+		prepare_foreign_modify(fmstate);
+
+	nattrs = setup_exec_prepared(resultRelInfo,
+								 NULL, slot,
+								 p_values, p_lengths);
+	Assert(fmstate->p_nums == nattrs);
+
+	res = PQexecPrepared(fmstate->conn,
+						 fmstate->p_name,
+						 nattrs,
+						 p_values,
+						 p_lengths,
+						 NULL, 0);
+	if (!res || (!fmstate->has_returning ?
+				 PQresultStatus(res) != PGRES_COMMAND_OK :
+				 PQresultStatus(res) != PGRES_TUPLES_OK))
+		elog(ERROR, "could not execute prepared statement (%s): %s",
+			 fmstate->query, PQerrorMessage(fmstate->conn));
+	n_rows = atoi(PQcmdTuples(res));
+	if (n_rows > 0 && fmstate->has_returning)
+		store_returning_result(fmstate, slot, res);
+	PQclear(res);
+
+	return (n_rows > 0 ? slot : NULL);
+}
+
+static bool
+postgresExecForeignDelete(ResultRelInfo *resultRelInfo, const char *rowid)
+{
+	PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state;
+	const char	   *p_values[1];
+	int				p_lengths[1];
+	AttrNumber		nattrs;
+	PGresult	   *res;
+	int				n_rows;
+
+	if (!fmstate->p_name)
+		prepare_foreign_modify(fmstate);
+
+	nattrs = setup_exec_prepared(resultRelInfo,
+								 rowid, NULL,
+								 p_values, p_lengths);
+	Assert(fmstate->p_nums == nattrs);
+
+	res = PQexecPrepared(fmstate->conn,
+						 fmstate->p_name,
+						 nattrs,
+						 p_values,
+						 p_lengths,
+						 NULL, 0);
+	if (!res ||  PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		PQclear(res);
+		elog(ERROR, "could not execute prepared statement (%s): %s",
+			 fmstate->query, PQerrorMessage(fmstate->conn));
+    }
+	n_rows = atoi(PQcmdTuples(res));
+    PQclear(res);
+
+	return (n_rows > 0 ? true : false);
+}
+
+static TupleTableSlot*
+postgresExecForeignUpdate(ResultRelInfo *resultRelInfo,
+						  const char *rowid, TupleTableSlot *slot)
+{
+	PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state;
+	const char	  **p_values  = alloca(sizeof(char *) * (fmstate->p_nums + 1));
+	int			   *p_lengths = alloca(sizeof(int) * (fmstate->p_nums + 1));
+	AttrNumber		nattrs;
+	PGresult	   *res;
+	int				n_rows;
+
+	if (!fmstate->p_name)
+		prepare_foreign_modify(fmstate);
+
+	nattrs = setup_exec_prepared(resultRelInfo,
+								 rowid, slot,
+								 p_values, p_lengths);
+	Assert(fmstate->p_nums == nattrs);
+
+	res = PQexecPrepared(fmstate->conn,
+						 fmstate->p_name,
+						 nattrs,
+						 p_values,
+						 p_lengths,
+						 NULL, 0);
+	if (!res || (!fmstate->has_returning ?
+				 PQresultStatus(res) != PGRES_COMMAND_OK :
+				 PQresultStatus(res) != PGRES_TUPLES_OK))
+	{
+		PQclear(res);
+		elog(ERROR, "could not execute prepared statement (%s): %s",
+			 fmstate->query, PQerrorMessage(fmstate->conn));
+	}
+	n_rows = atoi(PQcmdTuples(res));
+	if (n_rows > 0 && fmstate->has_returning)
+		store_returning_result(fmstate, slot, res);
+	PQclear(res);
+
+	return (n_rows > 0 ? slot : NULL);
+}
+
+static void
+postgresEndForeignModify(ResultRelInfo *resultRelInfo)
+{
+	PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state;
+
+	ReleaseConnection(fmstate->conn, false);
+	fmstate->conn = NULL;
+}
diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h
index b5cefb8..8225013 100644
--- a/contrib/postgres_fdw/postgres_fdw.h
+++ b/contrib/postgres_fdw/postgres_fdw.h
@@ -30,7 +30,8 @@ int GetFetchCountOption(ForeignTable *table, ForeignServer *server);
 void deparseSimpleSql(StringInfo buf,
 					  PlannerInfo *root,
 					  RelOptInfo *baserel,
-					  List *local_conds);
+					  List *local_conds,
+					  AttrNumber anum_rowid);
 void appendWhereClause(StringInfo buf,
 					   bool has_where,
 					   List *exprs,
@@ -41,5 +42,10 @@ void classifyConditions(PlannerInfo *root,
 						List **param_conds,
 						List **local_conds);
 void deparseAnalyzeSql(StringInfo buf, Relation rel);
+void deparseInsertSql(StringInfo buf, PlannerInfo *root, Index rtindex,
+					  List *targetAttrs, bool has_returning);
+void deparseUpdateSql(StringInfo buf, PlannerInfo *root, Index rtindex,
+					  List *targetAttrs, bool has_returning);
+void deparseDeleteSql(StringInfo buf, PlannerInfo *root, Index rtindex);
 
 #endif /* POSTGRESQL_FDW_H */
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 7845e70..89e6cf4 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -300,6 +300,37 @@ ERROR OUT;          -- ERROR
 SELECT srvname FROM postgres_fdw_connections;
 
 -- ===================================================================
+-- test for writable foreign table stuff (PoC stage now)
+-- ===================================================================
+EXPLAIN(verbose) 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) 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) 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;
+
+-- In case of remote table has before-row trigger or default with returning
+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 *;
+
+-- ===================================================================
 -- cleanup
 -- ===================================================================
 DROP OPERATOR === (int, int) CASCADE;
pgsql-v9.3-writable-fdw-poc.v12.part-1.patchapplication/octet-stream; name=pgsql-v9.3-writable-fdw-poc.v12.part-1.patchDownload
 doc/src/sgml/ddl.sgml                   |   5 -
 doc/src/sgml/fdwhandler.sgml            | 170 +++++++++++++++++++++++++++++++-
 src/backend/executor/execMain.c         |  34 ++++++-
 src/backend/executor/nodeForeignscan.c  | 136 ++++++++++++++++++++++++-
 src/backend/executor/nodeModifyTable.c  | 163 +++++++++++++++++++++++++-----
 src/backend/foreign/foreign.c           |  68 +++++++++++++
 src/backend/nodes/copyfuncs.c           |   2 +
 src/backend/nodes/outfuncs.c            |   2 +
 src/backend/optimizer/plan/createplan.c |  64 +++++++++++-
 src/backend/optimizer/plan/initsplan.c  |  14 ++-
 src/backend/optimizer/plan/planmain.c   |   5 +-
 src/backend/optimizer/plan/planner.c    |  42 ++------
 src/backend/optimizer/prep/prepunion.c  |   3 +-
 src/backend/optimizer/util/plancat.c    |  27 ++++-
 src/backend/optimizer/util/relnode.c    |   7 +-
 src/backend/parser/parse_relation.c     |  15 ++-
 src/backend/rewrite/rewriteHandler.c    |  55 +++++++++--
 src/backend/utils/adt/ruleutils.c       |  16 ++-
 src/include/foreign/fdwapi.h            |  31 ++++++
 src/include/foreign/foreign.h           |   6 ++
 src/include/nodes/execnodes.h           |  13 ++-
 src/include/nodes/plannodes.h           |   2 +
 src/include/optimizer/pathnode.h        |   2 +-
 src/include/optimizer/plancat.h         |   2 +-
 src/include/optimizer/planmain.h        |   8 +-
 25 files changed, 788 insertions(+), 104 deletions(-)

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 207de9b..8f44d8d 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -3066,11 +3066,6 @@ ANALYZE measurement;
     <firstterm>user mapping</>, which can provide additional options 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.
-   </para>
  </sect1>
 
  <sect1 id="ddl-others">
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index 638b6ab..731016f 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -89,6 +89,54 @@
 
     <para>
 <programlisting>
+AttrNumber
+GetForeignRelWidth(PlannerInfo *root,
+                   RelOptInfo *baserel,
+                   Relation foreignrel,
+                   bool inhparent,
+                   List *targetList);
+</programlisting>
+     Obtain the width of the result set to be fetched during a foreign table scan.
+     This is an optional handler, and called before <literal>GetForeignRelSize</>
+     for a query involving a foreign table
+     (during the construction of <literal>RelOptInfo</>).
+     <literal>root</> is the planner's global information about the query,
+     <literal>baserel</> is the planner's information being constructed for
+     this query, and <literal>foreignrel</> is a <literal>Relation</>
+     descriptor of the foreign table.
+     <literal>inhparent</> is a boolean to show whether the relation is
+     an inheritance parent, even though foreign tables do not support table
+     inheritance right now. <literal>targetList</> is the list of
+     <literal>TargetEntry</> to be returned from the (sub-)query
+     that is currently in focus.
+    </para>
+
+    <para>
+     The result value of this function will be assigned to
+     <literal>baserel-&gt;max_attr</>, that means it is the expected number
+     of columns being fetched during the foreign table scan.
+     It should not be smaller than the number of regular columns in the definition
+     of this foreign table.  You can only return a number greater than his value to
+     acquire slots for some additional attributes, which are called
+     <firstterm>pseudo-columns</>.
+     A typical usage of a pseudo-column is to carry an identifier of
+     a particular remote row to be updated or deleted from the scanning stage
+     to the modifying stage when the foreign table is the target of
+     a data-modifying SQL statement.
+     You can return the result of the helper function
+     <literal>get_pseudo_rowid_column</> if this <literal>"rowid"</>
+     pseudo-column is the only one you need.
+   </para>
+
+   <para>
+     In addition to that, pseudo-columns can be used to off-load the burden of
+     complex calculations to foreign computing resources by replacing an
+     expression with a reference to its result, which is calculated on the
+     remote side rather than locally.
+   </para>
+
+    <para>
+<programlisting>
 void
 GetForeignRelSize (PlannerInfo *root,
                    RelOptInfo *baserel,
@@ -96,7 +144,8 @@ 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.
+     for a query involving a foreign table at the beginning of planning
+     or right after <literal>GetForeignRelWidth</>, if that callback is configured.
      <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
@@ -315,6 +364,125 @@ AcquireSampleRowsFunc (Relation relation, int elevel,
     </para>
 
     <para>
+     If a FDW supports writable foreign tables, it should implement
+     some or all of the following callback functions depending on
+     the needs and capabilities of the FDW.
+    </para>
+
+    <para>
+<programlisting>
+List *
+PlanForeignModify(PlannerInfo *root,
+                  ModifyTable *plan,
+                  Index resultRelation,
+                  Plan *subplan);
+</programlisting>
+     It allows FDW drivers to construct private information relevant to
+     the modification of the foreign table.  This private information must have
+     the form of a <literal>List *</>, which will be delivered as
+     third argument to <literal>BeginForeignModify</> during the execution stage.
+    </para>
+
+    <para>
+     <literal>root</> is the planner's global information about the query.
+     <literal>plan</> is the master plan to modify the result relation according
+     to its command type.  Please consider that <literal>ModifyTable</>
+     may have multiple result relations in a future revision, even though
+     currently there is no support for table inheritance on foreign tables.
+     <literal>resultRelation</> is an index for the result relation in the
+     range table entries, and <literal>subplan</> is the relevant scan plan;
+     that should be a <literal>ForeignScan</> for <literal>UPDATE</> or
+     <literal>DELETE</>, so the driver can access the private information of
+     the scan stage using this argument.
+    </para>
+
+    <para>
+<programlisting>
+void
+BeginForeignModify (ModifyTableState *mtstate,
+                    ResultRelInfo *resultRelInfo,
+                    List *fdw_private,
+                    Plan *subplan,
+                    int eflags);
+</programlisting>
+     It is invoked at beginning of foreign table modification, during
+     executor startup.  This routine should perform any initialization
+     needed prior to the actual table modifications, but not start
+     modifying the actual tuples. (That should be done during each call of
+     <function>ExecForeignInsert</>, <function>ExecForeignUpdate</> or
+     <function>ExecForeignDelete</>.)
+    </para>
+
+    <para>
+     The <structfield>ri_fdw_state</> field of <structname>ResultRelInfo</>
+     is reserved to store any private information relevant to this foreign table,
+     so it should be initialized to contain the per-scan state.
+     Please consider that <literal>ModifyTableState</> may have multiple
+     result relations in a future revision, even though currently there is no
+     support for table inheritance on foreign tables.  <literal>resultRelInfo</>
+     is the master information connected to this foreign table.
+     <literal>fdw_private</> is private information constructed in
+     <literal>PlanForeignModify</>, and <literal>subplan</> is the relevant
+     scan plan of this table modification.
+    </para>
+
+    <para>
+<programlisting>
+TupleTableSlot *
+ExecForeignInsert (ResultRelInfo *resultRelInfo,
+                   TupleTableSlot *slot);
+</programlisting>
+     Insert the given tuple into backing storage on behalf of the foreign table.
+     The supplied slot shall hold a tuple being already formed according to
+     the definition of the relation, thus all the pseudo-columns are already
+     filtered out.
+     FDW driver can modify the tuple to be inserted (due to before-row-insert
+     triggers at remote side, for example). In this case, this routine can
+     return a modified slot that affects result of <literal>RETURNING</>,
+     or <literal>NULL</> that means this insertion was skipped. Elsewhere,
+     it usually returns the given slot, as is.
+    </para>
+
+    <para>
+<programlisting>
+bool
+ExecForeignDelete (ResultRelInfo *resultRelInfo,
+                   const char *rowid);
+</programlisting>
+     Delete the tuple being identified with <literal>rowid</> from the backing
+     storage on behalf of the foreign table.
+     FDW driver can return a boolean that means whether this deletion was done
+     actually, or not.
+    </para>
+
+    <para>
+<programlisting>
+TupleTableSlot *
+ExecForeignUpdate (ResultRelInfo *resultRelInfo,
+                   const char *rowid,
+                   TupleTableSlot *slot);
+</programlisting>
+     Update the tuple being identified with <literal>rowid</> in the backing
+     storage on behalf of the foreign table with the given slot that shall
+     hold a newer version of tuple.
+     FDW driver can modify the tuple to be updated (due to before-row-update
+     triggers at remote side, for example). In this case, this routine can
+     return a modified slot that affects result of <literal>RETURNING</>,
+     or <literal>NULL</> that means this update was skipped. Elsewhere,
+     it usually returns the given slot, as is.
+    </para>
+
+    <para>
+<programlisting>
+void
+EndForeignModify (ResultRelInfo *resultRelInfo);
+</programlisting>
+     End the modification 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>
      The <structname>FdwRoutine</> struct type is declared in
      <filename>src/include/foreign/fdwapi.h</>, which see for additional
      details.
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 632644f..ce977ee 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"
@@ -938,6 +939,7 @@ void
 CheckValidResultRel(Relation resultRel, CmdType operation)
 {
 	TriggerDesc *trigDesc = resultRel->trigdesc;
+	FdwRoutine	*fdwroutine;
 
 	switch (resultRel->rd_rel->relkind)
 	{
@@ -996,10 +998,34 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
 			}
 			break;
 		case RELKIND_FOREIGN_TABLE:
-			ereport(ERROR,
-					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("cannot change foreign table \"%s\"",
-							RelationGetRelationName(resultRel))));
+			fdwroutine = GetFdwRoutineByRelId(RelationGetRelid(resultRel));
+			switch (operation)
+			{
+				case CMD_INSERT:
+					if (!fdwroutine || !fdwroutine->ExecForeignInsert)
+						ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("cannot insert into foreign table \"%s\"",
+									RelationGetRelationName(resultRel))));
+					break;
+				case CMD_UPDATE:
+					if (!fdwroutine || !fdwroutine->ExecForeignUpdate)
+						ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("cannot update foreign table \"%s\"",
+									RelationGetRelationName(resultRel))));
+					break;
+				case CMD_DELETE:
+					if (!fdwroutine || !fdwroutine->ExecForeignDelete)
+						ereport(ERROR,
+							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+							 errmsg("cannot delete from foreign table \"%s\"",
+									RelationGetRelationName(resultRel))));
+					break;
+				default:
+					elog(ERROR, "unrecognized CmdType: %d", (int) operation);
+					break;
+			}
 			break;
 		default:
 			ereport(ERROR,
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 6ebffad..4eaf2a9 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -25,6 +25,7 @@
 #include "executor/executor.h"
 #include "executor/nodeForeignscan.h"
 #include "foreign/fdwapi.h"
+#include "nodes/nodeFuncs.h"
 #include "utils/rel.h"
 
 static TupleTableSlot *ForeignNext(ForeignScanState *node);
@@ -93,6 +94,133 @@ ExecForeignScan(ForeignScanState *node)
 					(ExecScanRecheckMtd) ForeignRecheck);
 }
 
+/*
+ * pseudo_column_walker
+ *
+ * helper routine of GetPseudoTupleDesc. It pulls Var nodes that reference
+ * pseudo columns from targetlis of the relation
+ */
+typedef struct
+{
+	Relation	relation;
+	Index		varno;
+	List	   *pcolumns;
+	AttrNumber	max_attno;
+} pseudo_column_walker_context;
+
+static bool
+pseudo_column_walker(Node *node, pseudo_column_walker_context *context)
+{
+	if (node == NULL)
+		return false;
+	if (IsA(node, Var))
+	{
+		Var		   *var = (Var *) node;
+		ListCell   *cell;
+
+		if (var->varno == context->varno && var->varlevelsup == 0 &&
+			var->varattno > RelationGetNumberOfAttributes(context->relation))
+		{
+			foreach (cell, context->pcolumns)
+			{
+				Var	   *temp = lfirst(cell);
+
+				if (temp->varattno == var->varattno)
+				{
+					if (!equal(var, temp))
+						elog(ERROR, "asymmetric pseudo column appeared");
+					break;
+				}
+			}
+			if (!cell)
+			{
+				context->pcolumns = lappend(context->pcolumns, var);
+				if (var->varattno > context->max_attno)
+					context->max_attno = var->varattno;
+			}
+		}
+		return false;
+	}
+
+	/* Should not find an unplanned subquery */
+	Assert(!IsA(node, Query));
+
+	return expression_tree_walker(node, pseudo_column_walker,
+								  (void *)context);
+}
+
+/*
+ * GetPseudoTupleDesc
+ *
+ * It generates TupleDesc structure including pseudo-columns if required.
+ */
+static TupleDesc
+GetPseudoTupleDesc(ForeignScan *node, Relation relation)
+{
+	pseudo_column_walker_context context;
+	List	   *target_list = node->scan.plan.targetlist;
+	TupleDesc	tupdesc;
+	AttrNumber	attno;
+	ListCell   *cell;
+	ListCell   *prev;
+	bool		hasoid;
+
+	context.relation = relation;
+	context.varno = node->scan.scanrelid;
+	context.pcolumns = NIL;
+	context.max_attno = -1;
+
+	pseudo_column_walker((Node *)target_list, (void *)&context);
+	Assert(context.max_attno > RelationGetNumberOfAttributes(relation));
+
+	hasoid = RelationGetForm(relation)->relhasoids;
+	tupdesc = CreateTemplateTupleDesc(context.max_attno, hasoid);
+
+	for (attno = 1; attno <= context.max_attno; attno++)
+	{
+		/* case of regular columns */
+		if (attno <= RelationGetNumberOfAttributes(relation))
+		{
+			memcpy(tupdesc->attrs[attno - 1],
+				   RelationGetDescr(relation)->attrs[attno - 1],
+				   ATTRIBUTE_FIXED_PART_SIZE);
+			continue;
+		}
+
+		/* case of pseudo columns */
+		prev = NULL;
+		foreach (cell, context.pcolumns)
+		{
+			Var	   *var = lfirst(cell);
+
+			if (var->varattno == attno)
+			{
+				char		namebuf[NAMEDATALEN];
+
+				snprintf(namebuf, sizeof(namebuf),
+						 "pseudo_column_%d", attno);
+
+				TupleDescInitEntry(tupdesc,
+								   attno,
+								   namebuf,
+								   var->vartype,
+								   var->vartypmod,
+								   0);
+				TupleDescInitEntryCollation(tupdesc,
+											attno,
+											var->varcollid);
+				context.pcolumns
+					= list_delete_cell(context.pcolumns, cell, prev);
+				break;
+			}
+			prev = cell;
+		}
+		if (!cell)
+			elog(ERROR, "pseudo column %d of %s not in target list",
+				 attno, RelationGetRelationName(relation));
+	}
+	return tupdesc;
+}
 
 /* ----------------------------------------------------------------
  *		ExecInitForeignScan
@@ -103,6 +231,7 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
 {
 	ForeignScanState *scanstate;
 	Relation	currentRelation;
+	TupleDesc	tupdesc;
 	FdwRoutine *fdwroutine;
 
 	/* check for unsupported flags */
@@ -149,7 +278,12 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
 	/*
 	 * get the scan type from the relation descriptor.
 	 */
-	ExecAssignScanType(&scanstate->ss, RelationGetDescr(currentRelation));
+	if (node->fsPseudoCol)
+		tupdesc = GetPseudoTupleDesc(node, currentRelation);
+	else
+		tupdesc = RelationGetDescr(currentRelation);
+
+	ExecAssignScanType(&scanstate->ss, tupdesc);
 
 	/*
 	 * Initialize result tuple type and projection info.
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index cb084d0..9572b3a 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,20 @@ ExecInsert(TupleTableSlot *slot,
 
 		newId = InvalidOid;
 	}
+	else if (resultRelInfo->ri_fdwroutine)
+	{
+		FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+		slot = fdwroutine->ExecForeignInsert(resultRelInfo, slot);
+
+		if (slot == NULL)		/* "do nothing" */
+			return NULL;
+
+		/* FDW driver might have changed tuple */
+		tuple = ExecMaterializeSlot(slot);
+
+		newId = InvalidOid;
+	}
 	else
 	{
 		/*
@@ -252,7 +267,7 @@ ExecInsert(TupleTableSlot *slot,
 
 	if (canSetTag)
 	{
-		(estate->es_processed)++;
+		(estate->es_processed) ++;
 		estate->es_lastoid = newId;
 		setLastTid(&(tuple->t_self));
 	}
@@ -285,7 +300,7 @@ ExecInsert(TupleTableSlot *slot,
  * ----------------------------------------------------------------
  */
 static TupleTableSlot *
-ExecDelete(ItemPointer tupleid,
+ExecDelete(Datum rowid,
 		   HeapTupleHeader oldtuple,
 		   TupleTableSlot *planSlot,
 		   EPQState *epqstate,
@@ -310,7 +325,7 @@ ExecDelete(ItemPointer tupleid,
 		bool		dodelete;
 
 		dodelete = ExecBRDeleteTriggers(estate, epqstate, resultRelInfo,
-										tupleid);
+										(ItemPointer)DatumGetPointer(rowid));
 
 		if (!dodelete)			/* "do nothing" */
 			return NULL;
@@ -334,8 +349,20 @@ ExecDelete(ItemPointer tupleid,
 		if (!dodelete)			/* "do nothing" */
 			return NULL;
 	}
+	else if (resultRelInfo->ri_fdwroutine)
+	{
+		FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+		bool		dodelete;
+
+		dodelete = fdwroutine->ExecForeignDelete(resultRelInfo,
+												 DatumGetCString(rowid));
+		if (!dodelete)
+			return NULL;		/* "do nothing" */
+	}
 	else
 	{
+		ItemPointer	tupleid = (ItemPointer) DatumGetPointer(rowid);
+
 		/*
 		 * delete the tuple
 		 *
@@ -431,10 +458,11 @@ ldelete:;
 	}
 
 	if (canSetTag)
-		(estate->es_processed)++;
+		(estate->es_processed) ++;
 
 	/* AFTER ROW DELETE Triggers */
-	ExecARDeleteTriggers(estate, resultRelInfo, tupleid);
+	ExecARDeleteTriggers(estate, resultRelInfo,
+						 (ItemPointer)DatumGetPointer(rowid));
 
 	/* Process RETURNING if present */
 	if (resultRelInfo->ri_projectReturning)
@@ -458,7 +486,8 @@ ldelete:;
 		}
 		else
 		{
-			deltuple.t_self = *tupleid;
+			ItemPointerCopy((ItemPointer)DatumGetPointer(rowid),
+							&deltuple.t_self);
 			if (!heap_fetch(resultRelationDesc, SnapshotAny,
 							&deltuple, &delbuffer, false, NULL))
 				elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
@@ -500,7 +529,7 @@ ldelete:;
  * ----------------------------------------------------------------
  */
 static TupleTableSlot *
-ExecUpdate(ItemPointer tupleid,
+ExecUpdate(Datum rowid,
 		   HeapTupleHeader oldtuple,
 		   TupleTableSlot *slot,
 		   TupleTableSlot *planSlot,
@@ -538,7 +567,7 @@ ExecUpdate(ItemPointer tupleid,
 		resultRelInfo->ri_TrigDesc->trig_update_before_row)
 	{
 		slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
-									tupleid, slot);
+									(ItemPointer)DatumGetPointer(rowid), slot);
 
 		if (slot == NULL)		/* "do nothing" */
 			return NULL;
@@ -568,9 +597,23 @@ ExecUpdate(ItemPointer tupleid,
 		/* trigger might have changed tuple */
 		tuple = ExecMaterializeSlot(slot);
 	}
+	else if (resultRelInfo->ri_fdwroutine)
+	{
+		FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+		slot = fdwroutine->ExecForeignUpdate(resultRelInfo,
+											 DatumGetCString(rowid),
+											 slot);
+		if (slot == NULL)		/* "do nothing" */
+			return NULL;
+
+		/* FDW driver might have changed tuple */
+		tuple = ExecMaterializeSlot(slot);
+	}
 	else
 	{
 		LockTupleMode	lockmode;
+		ItemPointer	tupleid = (ItemPointer) DatumGetPointer(rowid);
 
 		/*
 		 * Check the constraints of the tuple
@@ -691,11 +734,12 @@ lreplace:;
 	}
 
 	if (canSetTag)
-		(estate->es_processed)++;
+		(estate->es_processed) ++;
 
 	/* AFTER ROW UPDATE Triggers */
-	ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple,
-						 recheckIndexes);
+	ExecARUpdateTriggers(estate, resultRelInfo,
+						 (ItemPointer) DatumGetPointer(rowid),
+						 tuple, recheckIndexes);
 
 	list_free(recheckIndexes);
 
@@ -775,6 +819,7 @@ ExecModifyTable(ModifyTableState *node)
 	TupleTableSlot *planSlot;
 	ItemPointer tupleid = NULL;
 	ItemPointerData tuple_ctid;
+	Datum		rowid = 0;
 	HeapTupleHeader oldtuple = NULL;
 
 	/*
@@ -863,17 +908,19 @@ ExecModifyTable(ModifyTableState *node)
 		if (junkfilter != NULL)
 		{
 			/*
-			 * extract the 'ctid' or 'wholerow' junk attribute.
+			 * extract the 'ctid', 'rowid' or 'wholerow' junk attribute.
 			 */
 			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,
+												 junkfilter->jf_junkRowidNo,
 												 &isNull);
 					/* shouldn't ever get a null result... */
 					if (isNull)
@@ -881,13 +928,33 @@ ExecModifyTable(ModifyTableState *node)
 
 					tupleid = (ItemPointer) DatumGetPointer(datum);
 					tuple_ctid = *tupleid;		/* be sure we don't free
-												 * ctid!! */
-					tupleid = &tuple_ctid;
+												 * ctid ! */
+					rowid = PointerGetDatum(&tuple_ctid);
+				}
+				else if (relkind == RELKIND_FOREIGN_TABLE)
+				{
+					datum = ExecGetJunkAttribute(slot,
+												 junkfilter->jf_junkRowidNo,
+												 &isNull);
+					/* shouldn't ever get a null result... */
+					if (isNull)
+						elog(ERROR, "rowid is NULL");
+
+					rowid = datum;
+
+					datum = ExecGetJunkAttribute(slot,
+												 junkfilter->jf_junkRecordNo,
+												 &isNull);
+					/* shouldn't ever get a null result... */
+					if (isNull)
+						elog(ERROR, "wholerow is NULL");
+
+					oldtuple = DatumGetHeapTupleHeader(datum);
 				}
 				else
 				{
 					datum = ExecGetJunkAttribute(slot,
-												 junkfilter->jf_junkAttNo,
+												 junkfilter->jf_junkRecordNo,
 												 &isNull);
 					/* shouldn't ever get a null result... */
 					if (isNull)
@@ -910,11 +977,11 @@ ExecModifyTable(ModifyTableState *node)
 				slot = ExecInsert(slot, planSlot, estate, node->canSetTag);
 				break;
 			case CMD_UPDATE:
-				slot = ExecUpdate(tupleid, oldtuple, slot, planSlot,
+				slot = ExecUpdate(rowid, oldtuple, slot, planSlot,
 								&node->mt_epqstate, estate, node->canSetTag);
 				break;
 			case CMD_DELETE:
-				slot = ExecDelete(tupleid, oldtuple, planSlot,
+				slot = ExecDelete(rowid, oldtuple, planSlot,
 								&node->mt_epqstate, estate, node->canSetTag);
 				break;
 			default:
@@ -1001,6 +1068,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	i = 0;
 	foreach(l, node->plans)
 	{
+		char	relkind;
+
 		subplan = (Plan *) lfirst(l);
 
 		/*
@@ -1026,6 +1095,24 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		estate->es_result_relation_info = resultRelInfo;
 		mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
 
+		/*
+		 * Also tells FDW extensions to init the plan for this result rel
+		 */
+		relkind = RelationGetForm(resultRelInfo->ri_RelationDesc)->relkind;
+		if (relkind == RELKIND_FOREIGN_TABLE)
+		{
+			Oid		relid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+			FdwRoutine *fdwroutine = GetFdwRoutineByRelId(relid);
+			List   *fdwprivate = list_nth(node->fdwPrivList, i);
+
+			Assert(fdwroutine != NULL);
+			resultRelInfo->ri_fdwroutine = fdwroutine;
+			resultRelInfo->ri_fdw_state = NULL;
+
+			if (fdwroutine->BeginForeignModify)
+				fdwroutine->BeginForeignModify(mtstate, resultRelInfo,
+											   fdwprivate, subplan, eflags);
+		}
 		resultRelInfo++;
 		i++;
 	}
@@ -1167,6 +1254,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			for (i = 0; i < nplans; i++)
 			{
 				JunkFilter *j;
+				char		relkind =
+				    RelationGetForm(resultRelInfo->ri_RelationDesc)->relkind;
 
 				subplan = mtstate->mt_plans[i]->plan;
 				if (operation == CMD_INSERT || operation == CMD_UPDATE)
@@ -1180,16 +1269,27 @@ 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)
+					if (relkind == RELKIND_RELATION)
 					{
-						j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
-						if (!AttributeNumberIsValid(j->jf_junkAttNo))
+						j->jf_junkRowidNo = ExecFindJunkAttribute(j, "ctid");
+						if (!AttributeNumberIsValid(j->jf_junkRowidNo))
 							elog(ERROR, "could not find junk ctid column");
 					}
+					else if (relkind == RELKIND_FOREIGN_TABLE)
+					{
+						j->jf_junkRowidNo = ExecFindJunkAttribute(j, "rowid");
+						if (!AttributeNumberIsValid(j->jf_junkRowidNo))
+							elog(ERROR, "could not find junk rowid column");
+						j->jf_junkRecordNo
+							= ExecFindJunkAttribute(j, "record");
+						if (!AttributeNumberIsValid(j->jf_junkRecordNo))
+							elog(ERROR, "could not find junk wholerow column");
+					}
 					else
 					{
-						j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
-						if (!AttributeNumberIsValid(j->jf_junkAttNo))
+						j->jf_junkRecordNo
+							= ExecFindJunkAttribute(j, "wholerow");
+						if (!AttributeNumberIsValid(j->jf_junkRecordNo))
 							elog(ERROR, "could not find junk wholerow column");
 					}
 				}
@@ -1243,6 +1343,21 @@ ExecEndModifyTable(ModifyTableState *node)
 {
 	int			i;
 
+	/* Let the FDW shut dowm */
+	for (i=0; i < node->ps.state->es_num_result_relations; i++)
+	{
+		ResultRelInfo  *resultRelInfo
+			= &node->ps.state->es_result_relations[i];
+
+		if (resultRelInfo->ri_fdwroutine)
+		{
+			FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+			if (fdwroutine->EndForeignModify)
+				fdwroutine->EndForeignModify(resultRelInfo);
+		}
+	}
+
 	/*
 	 * Free the exprcontext
 	 */
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
index 872ed1f..cdeeb03 100644
--- a/src/backend/foreign/foreign.c
+++ b/src/backend/foreign/foreign.c
@@ -571,3 +571,71 @@ get_foreign_server_oid(const char *servername, bool missing_ok)
 				 errmsg("server \"%s\" does not exist", servername)));
 	return oid;
 }
+
+/*
+ * get_pseudo_rowid_column
+ *
+ * It picks up an attribute number to be used for the pseudo rowid column,
+ * if it exists.  It should be injected at rewriteHandler.c if the supplied query
+ * is a UPDATE or DELETE command.  Elsewhere, it returns InvalidAttrNumber.
+ */
+AttrNumber
+get_pseudo_rowid_column(RelOptInfo *baserel, List *targetList)
+{
+	ListCell   *cell;
+
+	foreach (cell, targetList)
+	{
+		TargetEntry *tle = lfirst(cell);
+
+		if (tle->resjunk &&
+			tle->resname && strcmp(tle->resname, "rowid") == 0)
+		{
+			Var	   *var;
+
+			if (!IsA(tle->expr, Var))
+				elog(ERROR, "unexpected node on junk rowid entry: %d",
+					 (int) nodeTag(tle->expr));
+
+			var = (Var *) tle->expr;
+			if (baserel->relid == var->varno)
+				return var->varattno;
+		}
+	}
+	return InvalidAttrNumber;
+}
+
+/*
+ * lookup_foreign_scan_plan
+ *
+ * It looks up the ForeignScan plan node with the supplied range-table id.
+ * Its typical usage is for PlanForeignModify to get the underlying scan plan on
+ * UPDATE or DELETE commands.
+ */
+ForeignScan *
+lookup_foreign_scan_plan(Plan *subplan, Index rtindex)
+{
+	if (!subplan)
+		return NULL;
+
+	else if (IsA(subplan, ForeignScan))
+	{
+		ForeignScan	   *fscan = (ForeignScan *) subplan;
+
+		if (fscan->scan.scanrelid == rtindex)
+			return fscan;
+	}
+	else
+	{
+		ForeignScan    *fscan;
+
+		fscan = lookup_foreign_scan_plan(subplan->lefttree, rtindex);
+		if (fscan != NULL)
+			return fscan;
+
+		fscan = lookup_foreign_scan_plan(subplan->righttree, rtindex);
+		if (fscan != NULL)
+			return fscan;
+	}
+	return NULL;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 2da08d1..3593ddf 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -181,6 +181,7 @@ _copyModifyTable(const ModifyTable *from)
 	COPY_NODE_FIELD(returningLists);
 	COPY_NODE_FIELD(rowMarks);
 	COPY_SCALAR_FIELD(epqParam);
+	COPY_NODE_FIELD(fdwPrivList);
 
 	return newnode;
 }
@@ -594,6 +595,7 @@ _copyForeignScan(const ForeignScan *from)
 	COPY_NODE_FIELD(fdw_exprs);
 	COPY_NODE_FIELD(fdw_private);
 	COPY_SCALAR_FIELD(fsSystemCol);
+	COPY_SCALAR_FIELD(fsPseudoCol);
 
 	return newnode;
 }
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index ffd123d..6ab2dcd 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -335,6 +335,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
 	WRITE_NODE_FIELD(returningLists);
 	WRITE_NODE_FIELD(rowMarks);
 	WRITE_INT_FIELD(epqParam);
+	WRITE_NODE_FIELD(fdwPrivList);
 }
 
 static void
@@ -562,6 +563,7 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
 	WRITE_NODE_FIELD(fdw_exprs);
 	WRITE_NODE_FIELD(fdw_private);
 	WRITE_BOOL_FIELD(fsSystemCol);
+	WRITE_BOOL_FIELD(fsPseudoCol);
 }
 
 static void
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 8a51b56..32a6070 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -39,6 +39,7 @@
 #include "parser/parse_clause.h"
 #include "parser/parsetree.h"
 #include "utils/lsyscache.h"
+#include "utils/rel.h"
 
 
 static Plan *create_plan_recurse(PlannerInfo *root, Path *best_path);
@@ -1943,6 +1944,8 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 	RelOptInfo *rel = best_path->path.parent;
 	Index		scan_relid = rel->relid;
 	RangeTblEntry *rte;
+	Relation	relation;
+	AttrNumber	num_attrs;
 	int			i;
 
 	/* it should be a base rel... */
@@ -2001,6 +2004,22 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
 		}
 	}
 
+	/*
+	 * Also, detect whether any pseudo columns are requested from rel.
+	 */
+	relation = heap_open(rte->relid, NoLock);
+	scan_plan->fsPseudoCol = false;
+	num_attrs = RelationGetNumberOfAttributes(relation);
+	for (i = num_attrs + 1; i <= rel->max_attr; i++)
+	{
+		if (!bms_is_empty(rel->attr_needed[i - rel->min_attr]))
+		{
+			scan_plan->fsPseudoCol = true;
+			break;
+		}
+	}
+	heap_close(relation, NoLock);
+
 	return scan_plan;
 }
 
@@ -4695,15 +4714,30 @@ make_result(PlannerInfo *root,
  * to make it look better sometime.
  */
 ModifyTable *
-make_modifytable(CmdType operation, bool canSetTag,
+make_modifytable(PlannerInfo *root,
 				 List *resultRelations,
-				 List *subplans, List *returningLists,
-				 List *rowMarks, int epqParam)
+				 List *subplans, List *returningLists)
 {
 	ModifyTable *node = makeNode(ModifyTable);
 	Plan	   *plan = &node->plan;
 	double		total_size;
 	ListCell   *subnode;
+	ListCell   *resultRel;
+	List	   *fdw_priv_list = NIL;
+	CmdType		operation = root->parse->commandType;
+	bool		canSetTag = root->parse->canSetTag;
+	List	   *rowMarks;
+	int			epqParam = SS_assign_special_param(root);
+
+	/*
+	 * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node will
+	 * have dealt with fetching non-locked marked rows, else we need
+	 * to have ModifyTable do that.
+	 */
+	if (root->parse->rowMarks)
+		rowMarks = NIL;
+	else
+		rowMarks = root->rowMarks;
 
 	Assert(list_length(resultRelations) == list_length(subplans));
 	Assert(returningLists == NIL ||
@@ -4746,6 +4780,30 @@ make_modifytable(CmdType operation, bool canSetTag,
 	node->rowMarks = rowMarks;
 	node->epqParam = epqParam;
 
+	/*
+	 * Allow FDW driver to construct its private plan if the result relation
+	 * is a foreign table.
+	 */
+	forboth (resultRel, resultRelations, subnode, subplans)
+	{
+		RangeTblEntry  *rte = rt_fetch(lfirst_int(resultRel),
+									   root->parse->rtable);
+		List		   *fdw_private = NIL;
+		char			relkind = get_rel_relkind(rte->relid);
+
+		if (relkind == RELKIND_FOREIGN_TABLE)
+		{
+			FdwRoutine *fdwroutine = GetFdwRoutineByRelId(rte->relid);
+
+			if (fdwroutine && fdwroutine->PlanForeignModify)
+				fdw_private = fdwroutine->PlanForeignModify(root, node,
+													lfirst_int(resultRel),
+													(Plan *) lfirst(subnode));
+		}
+		fdw_priv_list = lappend(fdw_priv_list, fdw_private);
+	}
+	node->fdwPrivList = fdw_priv_list;
+
 	return node;
 }
 
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 84ca674..e4f8d9f 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -83,9 +83,13 @@ static void check_hashjoinable(RestrictInfo *restrictinfo);
  * is the only place that should call build_simple_rel with reloptkind
  * RELOPT_BASEREL.	(Note: build_simple_rel recurses internally to build
  * "other rel" RelOptInfos for the members of any appendrels we find here.)
+ *
+ * XXX - Also note that tlist needs to be pushed down into deeper level,
+ * for construction of RelOptInfo relevant to foreign-tables with pseudo-
+ * columns.
  */
 void
-add_base_rels_to_query(PlannerInfo *root, Node *jtnode)
+add_base_rels_to_query(PlannerInfo *root, List *tlist, Node *jtnode)
 {
 	if (jtnode == NULL)
 		return;
@@ -93,7 +97,7 @@ add_base_rels_to_query(PlannerInfo *root, Node *jtnode)
 	{
 		int			varno = ((RangeTblRef *) jtnode)->rtindex;
 
-		(void) build_simple_rel(root, varno, RELOPT_BASEREL);
+		(void) build_simple_rel(root, varno, tlist, RELOPT_BASEREL);
 	}
 	else if (IsA(jtnode, FromExpr))
 	{
@@ -101,14 +105,14 @@ add_base_rels_to_query(PlannerInfo *root, Node *jtnode)
 		ListCell   *l;
 
 		foreach(l, f->fromlist)
-			add_base_rels_to_query(root, lfirst(l));
+			add_base_rels_to_query(root, tlist, lfirst(l));
 	}
 	else if (IsA(jtnode, JoinExpr))
 	{
 		JoinExpr   *j = (JoinExpr *) jtnode;
 
-		add_base_rels_to_query(root, j->larg);
-		add_base_rels_to_query(root, j->rarg);
+		add_base_rels_to_query(root, tlist, j->larg);
+		add_base_rels_to_query(root, tlist, j->rarg);
 	}
 	else
 		elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
index a919914..0f26f6f 100644
--- a/src/backend/optimizer/plan/planmain.c
+++ b/src/backend/optimizer/plan/planmain.c
@@ -112,6 +112,9 @@ query_planner(PlannerInfo *root, List *tlist,
 	 */
 	if (parse->jointree->fromlist == NIL)
 	{
+		/* Make a flattened version of the rangetable for faster access */
+		setup_simple_rel_arrays(root);
+
 		/* We need a trivial path result */
 		*cheapest_path = (Path *)
 			create_result_path((List *) parse->jointree->quals);
@@ -163,7 +166,7 @@ query_planner(PlannerInfo *root, List *tlist,
 	 * rangetable may contain RTEs for rels not actively part of the query,
 	 * for example views.  We don't want to make RelOptInfos for them.
 	 */
-	add_base_rels_to_query(root, (Node *) parse->jointree);
+	add_base_rels_to_query(root, tlist, (Node *) parse->jointree);
 
 	/*
 	 * Examine the targetlist and join tree, adding entries to baserel
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 5b97cb5..624644f 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -551,7 +551,6 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 		if (parse->commandType != CMD_SELECT)
 		{
 			List	   *returningLists;
-			List	   *rowMarks;
 
 			/*
 			 * Set up the RETURNING list-of-lists, if needed.
@@ -561,23 +560,12 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 			else
 				returningLists = NIL;
 
-			/*
-			 * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node will
-			 * have dealt with fetching non-locked marked rows, else we need
-			 * to have ModifyTable do that.
-			 */
-			if (parse->rowMarks)
-				rowMarks = NIL;
-			else
-				rowMarks = root->rowMarks;
-
-			plan = (Plan *) make_modifytable(parse->commandType,
-											 parse->canSetTag,
+			Assert(parse->commandType == root->parse->commandType);
+			Assert(parse->canSetTag == root->parse->canSetTag);
+			plan = (Plan *) make_modifytable(root,
 									   list_make1_int(parse->resultRelation),
 											 list_make1(plan),
-											 returningLists,
-											 rowMarks,
-											 SS_assign_special_param(root));
+											 returningLists);
 		}
 	}
 
@@ -761,7 +749,6 @@ inheritance_planner(PlannerInfo *root)
 	List	   *subplans = NIL;
 	List	   *resultRelations = NIL;
 	List	   *returningLists = NIL;
-	List	   *rowMarks;
 	ListCell   *lc;
 
 	/*
@@ -953,24 +940,13 @@ inheritance_planner(PlannerInfo *root)
 	root->simple_rel_array_size = save_rel_array_size;
 	root->simple_rel_array = save_rel_array;
 
-	/*
-	 * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node will have
-	 * dealt with fetching non-locked marked rows, else we need to have
-	 * ModifyTable do that.
-	 */
-	if (parse->rowMarks)
-		rowMarks = NIL;
-	else
-		rowMarks = root->rowMarks;
-
 	/* And last, tack on a ModifyTable node to do the UPDATE/DELETE work */
-	return (Plan *) make_modifytable(parse->commandType,
-									 parse->canSetTag,
+	Assert(parse->commandType == root->parse->commandType);
+	Assert(parse->canSetTag == root->parse->canSetTag);
+	return (Plan *) make_modifytable(root,
 									 resultRelations,
 									 subplans,
-									 returningLists,
-									 rowMarks,
-									 SS_assign_special_param(root));
+									 returningLists);
 }
 
 /*--------------------
@@ -3396,7 +3372,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 	setup_simple_rel_arrays(root);
 
 	/* Build RelOptInfo */
-	rel = build_simple_rel(root, 1, RELOPT_BASEREL);
+	rel = build_simple_rel(root, 1, NIL, RELOPT_BASEREL);
 
 	/* Locate IndexOptInfo for the target index */
 	indexInfo = NULL;
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index e249628..e2984a5 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -236,7 +236,8 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
 		 * used for anything here, but it carries the subroot data structures
 		 * forward to setrefs.c processing.
 		 */
-		rel = build_simple_rel(root, rtr->rtindex, RELOPT_BASEREL);
+		rel = build_simple_rel(root, rtr->rtindex, refnames_tlist,
+							   RELOPT_BASEREL);
 
 		/* plan_params should not be in use in current query level */
 		Assert(root->plan_params == NIL);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 01f988f..7e98cea 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -26,6 +26,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/heap.h"
+#include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/clauses.h"
@@ -81,7 +82,7 @@ static List *build_index_tlist(PlannerInfo *root, IndexOptInfo *index,
  */
 void
 get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
-				  RelOptInfo *rel)
+				  List *tlist, RelOptInfo *rel)
 {
 	Index		varno = rel->relid;
 	Relation	relation;
@@ -105,6 +106,30 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 	rel->max_attr = RelationGetNumberOfAttributes(relation);
 	rel->reltablespace = RelationGetForm(relation)->reltablespace;
 
+	/*
+	 * Adjust width of attr_needed slot in case FDW extension wants
+	 * to return pseudo-columns in addition to the columns in its
+	 * table definition.
+	 * GetForeignRelWidth, an optional FDW handler, enables a FDW
+	 * to save properties of a pseudo-column in its private field.
+	 * When the foreign table is the target of UPDATE/DELETE, the query rewriter
+	 * injects a "rowid" pseudo-column to track the remote row to be modified,
+	 * so the FDW has to track which varattno shall perform as "rowid".
+	 */
+	if (RelationGetForm(relation)->relkind == RELKIND_FOREIGN_TABLE)
+	{
+		FdwRoutine *fdwroutine = GetFdwRoutineByRelId(relationObjectId);
+
+		if (fdwroutine->GetForeignRelWidth)
+		{
+			rel->max_attr = fdwroutine->GetForeignRelWidth(root, rel,
+														   relation,
+														   inhparent,
+														   tlist);
+			Assert(rel->max_attr >= RelationGetNumberOfAttributes(relation));
+		}
+	}
+
 	Assert(rel->max_attr >= rel->min_attr);
 	rel->attr_needed = (Relids *)
 		palloc0((rel->max_attr - rel->min_attr + 1) * sizeof(Relids));
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 8ee5671..ababef5 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -80,7 +80,8 @@ setup_simple_rel_arrays(PlannerInfo *root)
  *	  Construct a new RelOptInfo for a base relation or 'other' relation.
  */
 RelOptInfo *
-build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
+build_simple_rel(PlannerInfo *root, int relid, List *tlist,
+				 RelOptKind reloptkind)
 {
 	RelOptInfo *rel;
 	RangeTblEntry *rte;
@@ -133,7 +134,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
 	{
 		case RTE_RELATION:
 			/* Table --- retrieve statistics from the system catalogs */
-			get_relation_info(root, rte->relid, rte->inh, rel);
+			get_relation_info(root, rte->relid, rte->inh, tlist, rel);
 			break;
 		case RTE_SUBQUERY:
 		case RTE_FUNCTION:
@@ -180,7 +181,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
 			if (appinfo->parent_relid != relid)
 				continue;
 
-			(void) build_simple_rel(root, appinfo->child_relid,
+			(void) build_simple_rel(root, appinfo->child_relid, tlist,
 									RELOPT_OTHER_MEMBER_REL);
 		}
 	}
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 93aeab8..f246d43 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -2087,7 +2087,20 @@ get_rte_attribute_name(RangeTblEntry *rte, AttrNumber attnum)
 	 * built (which can easily happen for rules).
 	 */
 	if (rte->rtekind == RTE_RELATION)
-		return get_relid_attribute_name(rte->relid, attnum);
+	{
+		char   *attname = get_attname(rte->relid, attnum);
+
+		if (attname)
+			return attname;
+
+		/*
+		 * XXX - If FDW driver adds pseudo-columns, it may have attribute
+		 * number larger than number of relation's attribute. In this case,
+		 * get_attname() returns NULL and we fall back on alias list on eref.
+		 * It should not happen other than foreign tables.
+		 */
+		Assert(get_rel_relkind(rte->relid) == RELKIND_FOREIGN_TABLE);
+	}
 
 	/*
 	 * Otherwise use the column name from eref.  There should always be one.
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index b458de6..f6494d8 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1165,7 +1165,10 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 					Relation target_relation)
 {
 	Var		   *var;
-	const char *attrname;
+	List	   *varList;
+	List	   *attNameList;
+	ListCell   *cell1;
+	ListCell   *cell2;
 	TargetEntry *tle;
 
 	if (target_relation->rd_rel->relkind == RELKIND_RELATION)
@@ -1179,8 +1182,35 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 					  -1,
 					  InvalidOid,
 					  0);
+		varList = list_make1(var);
+		attNameList = list_make1("ctid");
+	}
+	else if (target_relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+	{
+		/*
+		 * Emit Rowid so that executor can find the row to update or delete.
+		 */
+		var = makeVar(parsetree->resultRelation,
+					  RelationGetNumberOfAttributes(target_relation) + 1,
+					  CSTRINGOID,
+					  -2,
+					  InvalidOid,
+					  0);
+		varList = list_make1(var);
+
+		/*
+		 * Emit generic record Var so that executor will have the "old" view
+		 * row to pass the RETURNING clause (or upcoming triggers).
+		 */
+		var = makeVar(parsetree->resultRelation,
+					  InvalidAttrNumber,
+					  RECORDOID,
+					  -1,
+					  InvalidOid,
+					  0);
+		varList = lappend(varList, var);
 
-		attrname = "ctid";
+		attNameList = list_make2("rowid", "record");
 	}
 	else
 	{
@@ -1192,16 +1222,21 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
 							  parsetree->resultRelation,
 							  0,
 							  false);
-
-		attrname = "wholerow";
+		varList = list_make1(var);
+		attNameList = list_make1("wholerow");
 	}
 
-	tle = makeTargetEntry((Expr *) var,
-						  list_length(parsetree->targetList) + 1,
-						  pstrdup(attrname),
-						  true);
-
-	parsetree->targetList = lappend(parsetree->targetList, tle);
+	/*
+	 * Append them to targetList
+	 */
+	forboth (cell1, varList, cell2, attNameList)
+	{
+		tle = makeTargetEntry((Expr *)lfirst(cell1),
+							  list_length(parsetree->targetList) + 1,
+							  pstrdup((const char *)lfirst(cell2)),
+							  true);
+		parsetree->targetList = lappend(parsetree->targetList, tle);
+	}
 }
 
 
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index af04b05..31b211a 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -2872,16 +2872,28 @@ set_relation_column_names(deparse_namespace *dpns, RangeTblEntry *rte,
 		rel = relation_open(rte->relid, AccessShareLock);
 		tupdesc = RelationGetDescr(rel);
 
-		ncolumns = tupdesc->natts;
+		ncolumns = (tupdesc->natts < list_length(rte->eref->colnames)
+					? list_length(rte->eref->colnames)
+					: tupdesc->natts);
 		real_colnames = (char **) palloc(ncolumns * sizeof(char *));
 
-		for (i = 0; i < ncolumns; i++)
+		for (i = 0; i < tupdesc->natts; i++)
 		{
 			if (tupdesc->attrs[i]->attisdropped)
 				real_colnames[i] = NULL;
 			else
 				real_colnames[i] = pstrdup(NameStr(tupdesc->attrs[i]->attname));
 		}
+		/*
+		 * XXX - foreign table may have pseudo-column that has attribute
+		 * number larger than or equal with number of attributes in table
+		 * definition. So, we need to add entries for them.
+		 */
+		while (i < ncolumns)
+		{
+			real_colnames[i] = strVal(list_nth(rte->eref->colnames, i));
+			i++;
+		}
 		relation_close(rel, AccessShareLock);
 	}
 	else
diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
index 13dcbfd..fd99591 100644
--- a/src/include/foreign/fdwapi.h
+++ b/src/include/foreign/fdwapi.h
@@ -14,6 +14,7 @@
 
 #include "nodes/execnodes.h"
 #include "nodes/relation.h"
+#include "utils/rel.h"
 
 /* To avoid including explain.h here, reference ExplainState thus: */
 struct ExplainState;
@@ -22,6 +23,11 @@ struct ExplainState;
 /*
  * Callback function signatures --- see fdwhandler.sgml for more info.
  */
+typedef AttrNumber (*GetForeignRelWidth_function) (PlannerInfo *root,
+												   RelOptInfo *baserel,
+												   Relation foreignrel,
+												   bool inhparent,
+												   List *targetList);
 
 typedef void (*GetForeignRelSize_function) (PlannerInfo *root,
 														RelOptInfo *baserel,
@@ -58,6 +64,24 @@ typedef int (*AcquireSampleRowsFunc) (Relation relation, int elevel,
 typedef bool (*AnalyzeForeignTable_function) (Relation relation,
 												 AcquireSampleRowsFunc *func,
 													BlockNumber *totalpages);
+typedef List *(*PlanForeignModify_function) (PlannerInfo *root,
+											 ModifyTable *plan,
+											 Index resultRelation,
+											 Plan *subplan);
+
+typedef void (*BeginForeignModify_function) (ModifyTableState *mtstate,
+											 ResultRelInfo *rinfo,
+											 List *fdw_private,
+											 Plan *subplan,
+											 int eflags);
+typedef TupleTableSlot *(*ExecForeignInsert_function) (ResultRelInfo *rinfo,
+													   TupleTableSlot *slot);
+typedef TupleTableSlot *(*ExecForeignUpdate_function) (ResultRelInfo *rinfo,
+													   const char *rowid,
+													   TupleTableSlot *slot);
+typedef bool (*ExecForeignDelete_function) (ResultRelInfo *rinfo,
+											const char *rowid);
+typedef void (*EndForeignModify_function) (ResultRelInfo *rinfo);
 
 /*
  * FdwRoutine is the struct returned by a foreign-data wrapper's handler
@@ -90,6 +114,13 @@ typedef struct FdwRoutine
 	 * not provided.
 	 */
 	AnalyzeForeignTable_function AnalyzeForeignTable;
+	GetForeignRelWidth_function GetForeignRelWidth;
+	PlanForeignModify_function PlanForeignModify;
+	BeginForeignModify_function	BeginForeignModify;
+	ExecForeignInsert_function ExecForeignInsert;
+	ExecForeignDelete_function ExecForeignDelete;
+	ExecForeignUpdate_function ExecForeignUpdate;
+	EndForeignModify_function EndForeignModify;
 } FdwRoutine;
 
 
diff --git a/src/include/foreign/foreign.h b/src/include/foreign/foreign.h
index 5bd6ae6..749f9a6 100644
--- a/src/include/foreign/foreign.h
+++ b/src/include/foreign/foreign.h
@@ -14,6 +14,8 @@
 #define FOREIGN_H
 
 #include "nodes/parsenodes.h"
+#include "nodes/plannodes.h"
+#include "nodes/relation.h"
 
 
 /* Helper for obtaining username for user mapping */
@@ -81,4 +83,8 @@ extern List *GetForeignColumnOptions(Oid relid, AttrNumber attnum);
 extern Oid	get_foreign_data_wrapper_oid(const char *fdwname, bool missing_ok);
 extern Oid	get_foreign_server_oid(const char *servername, bool missing_ok);
 
+extern AttrNumber get_pseudo_rowid_column(RelOptInfo *baserel,
+										  List *targetList);
+extern ForeignScan *lookup_foreign_scan_plan(Plan *subplan,
+											 Index rtindex);
 #endif   /* FOREIGN_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 76e8cdb..97c2045 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -268,9 +268,11 @@ typedef struct ProjectionInfo
  *						attribute numbers of the "original" tuple and the
  *						attribute numbers of the "clean" tuple.
  *	  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
+ *	  jf_junkRowidNo:	not used by junkfilter code.  Can be used by caller
+ *						to remember the attno used to track a particular tuple
+ *						being updated or deleted.
  *						(execMain.c stores the "ctid" attno here).
+ *	  jf_junkRecordNo:	Also, the attno of whole-row reference.
  * ----------------
  */
 typedef struct JunkFilter
@@ -280,7 +282,8 @@ typedef struct JunkFilter
 	TupleDesc	jf_cleanTupType;
 	AttrNumber *jf_cleanMap;
 	TupleTableSlot *jf_resultSlot;
-	AttrNumber	jf_junkAttNo;
+	AttrNumber	jf_junkRowidNo;
+	AttrNumber	jf_junkRecordNo;
 } JunkFilter;
 
 /* ----------------
@@ -303,6 +306,8 @@ typedef struct JunkFilter
  *		ConstraintExprs			array of constraint-checking expr states
  *		junkFilter				for removing junk attributes from tuples
  *		projectReturning		for computing a RETURNING list
+ *		fdwroutine				FDW callbacks if foreign table
+ *		fdw_state				opaque state of FDW module, or NULL
  * ----------------
  */
 typedef struct ResultRelInfo
@@ -320,6 +325,8 @@ typedef struct ResultRelInfo
 	List	  **ri_ConstraintExprs;
 	JunkFilter *ri_junkFilter;
 	ProjectionInfo *ri_projectReturning;
+	struct FdwRoutine  *ri_fdwroutine;
+	void	   *ri_fdw_state;
 } ResultRelInfo;
 
 /* ----------------
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 0b8b107..1fd907d 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -175,6 +175,7 @@ typedef struct ModifyTable
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
 	int			epqParam;		/* ID of Param for EvalPlanQual re-eval */
+	List	   *fdwPrivList;	/* private fields for foreign tables */
 } ModifyTable;
 
 /* ----------------
@@ -478,6 +479,7 @@ typedef struct ForeignScan
 	List	   *fdw_exprs;		/* expressions that FDW may evaluate */
 	List	   *fdw_private;	/* private data for FDW */
 	bool		fsSystemCol;	/* true if any "system column" is needed */
+	bool		fsPseudoCol;	/* true if any "pseudo column" is needed */
 } ForeignScan;
 
 
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index bc68789..91b2983 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -133,7 +133,7 @@ extern Path *reparameterize_path(PlannerInfo *root, Path *path,
  */
 extern void setup_simple_rel_arrays(PlannerInfo *root);
 extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid,
-				 RelOptKind reloptkind);
+			   List *tlist, RelOptKind reloptkind);
 extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid);
 extern RelOptInfo *find_join_rel(PlannerInfo *root, Relids relids);
 extern RelOptInfo *build_join_rel(PlannerInfo *root,
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index 6abd92a..59f175d 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -26,7 +26,7 @@ extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook;
 
 
 extern void get_relation_info(PlannerInfo *root, Oid relationObjectId,
-				  bool inhparent, RelOptInfo *rel);
+				  bool inhparent, List *tlist, RelOptInfo *rel);
 
 extern void estimate_rel_size(Relation rel, int32 *attr_widths,
 				  BlockNumber *pages, double *tuples, double *allvisfrac);
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 2f9fcd5..b866103 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -79,9 +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,
-				 List *resultRelations, List *subplans, List *returningLists,
-				 List *rowMarks, int epqParam);
+extern ModifyTable *make_modifytable(PlannerInfo *root,
+				 List *resultRelations, List *subplans, List *returningLists);
 extern bool is_projection_capable_plan(Plan *plan);
 
 /*
@@ -90,7 +89,8 @@ extern bool is_projection_capable_plan(Plan *plan);
 extern int	from_collapse_limit;
 extern int	join_collapse_limit;
 
-extern void add_base_rels_to_query(PlannerInfo *root, Node *jtnode);
+extern void add_base_rels_to_query(PlannerInfo *root, List *tlist,
+								   Node *jtnode);
 extern void build_base_rel_tlists(PlannerInfo *root, List *final_tlist);
 extern void add_vars_to_targetlist(PlannerInfo *root, List *vars,
 					   Relids where_needed, bool create_new_ph);
#54Craig Ringer
craig@2ndquadrant.com
In reply to: Kohei KaiGai (#53)
Re: [v9.3] writable foreign tables

On 02/08/2013 01:03 AM, Kohei KaiGai wrote:

The attached patch adds Daniel's reworks on make_modifytable
invocation, and add a short comment on add_base_rels_to_query(). Rest
of portion has not been changed from the previous version.

How's this looking for 9.3? On-list discussion seems to have been
positive but inconclusive and time's running out. Do you think this can
be turned into a production-worthy feature in the next week or two?

A quick look at the patch shows that it includes reasonable-looking
documentation changes. I didn't see any regression test suite changes,
though; either I missed them or that's something that probably needs
addressing.

--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#55Tom Lane
tgl@sss.pgh.pa.us
In reply to: Craig Ringer (#54)
Re: [v9.3] writable foreign tables

Craig Ringer <craig@2ndquadrant.com> writes:

On 02/08/2013 01:03 AM, Kohei KaiGai wrote:

The attached patch adds Daniel's reworks on make_modifytable
invocation, and add a short comment on add_base_rels_to_query(). Rest
of portion has not been changed from the previous version.

How's this looking for 9.3? On-list discussion seems to have been
positive but inconclusive and time's running out. Do you think this can
be turned into a production-worthy feature in the next week or two?

I think it needs major changes. The portion against
contrib/postgres_fdw fails to apply at all, of course, but that's my
fault for having hacked so much on postgres_fdw before committing it.
More generally, I don't much like the approach to ctid-substitute
columns --- I think hacking on the rel's tupledesc like that is
guaranteed to break things all over the place. The assorted ugly
kluges that are already in the patch because of it are just scratching
the surface, and there may well be consequences that are flat out
unfixable. Probably the resjunk-columns mechanism would offer a better
solution.

I had hoped to spend several days on this and perhaps get it into
committable shape, because I think this is a pretty significant feature
that will take FDWs over the line from curiosity to useful tool.
However, I've been hoping that for nigh two weeks now and not actually
had any cycles to spend on it ...

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#56Craig Ringer
craig@2ndquadrant.com
In reply to: Tom Lane (#55)
Re: [v9.3] writable foreign tables

On 03/03/2013 11:17 PM, Tom Lane wrote:

Craig Ringer <craig@2ndquadrant.com> writes:

On 02/08/2013 01:03 AM, Kohei KaiGai wrote:

The attached patch adds Daniel's reworks on make_modifytable
invocation, and add a short comment on add_base_rels_to_query(). Rest
of portion has not been changed from the previous version.

How's this looking for 9.3? On-list discussion seems to have been
positive but inconclusive and time's running out. Do you think this can
be turned into a production-worthy feature in the next week or two?

I think it needs major changes. The portion against
contrib/postgres_fdw fails to apply at all, of course, but that's my
fault for having hacked so much on postgres_fdw before committing it.
More generally, I don't much like the approach to ctid-substitute
columns --- I think hacking on the rel's tupledesc like that is
guaranteed to break things all over the place. The assorted ugly
kluges that are already in the patch because of it are just scratching
the surface, and there may well be consequences that are flat out
unfixable. Probably the resjunk-columns mechanism would offer a better
solution.

I had hoped to spend several days on this and perhaps get it into
committable shape, because I think this is a pretty significant feature
that will take FDWs over the line from curiosity to useful tool.
However, I've been hoping that for nigh two weeks now and not actually
had any cycles to spend on it ...

Do you have any further brief suggestions for things that KaiGai Kohei
or others could do to make your side of this process easier and reduce
the amount of your time it'll demand?

For now it seems this stays in hopefully-can-be-made-ready limbo. I'll
keep looking through the list.

--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#57Kohei KaiGai
kaigai@kaigai.gr.jp
In reply to: Tom Lane (#55)
Re: [v9.3] writable foreign tables

2013/3/3 Tom Lane <tgl@sss.pgh.pa.us>:

Craig Ringer <craig@2ndquadrant.com> writes:

On 02/08/2013 01:03 AM, Kohei KaiGai wrote:

The attached patch adds Daniel's reworks on make_modifytable
invocation, and add a short comment on add_base_rels_to_query(). Rest
of portion has not been changed from the previous version.

How's this looking for 9.3? On-list discussion seems to have been
positive but inconclusive and time's running out. Do you think this can
be turned into a production-worthy feature in the next week or two?

I think it needs major changes. The portion against
contrib/postgres_fdw fails to apply at all, of course, but that's my
fault for having hacked so much on postgres_fdw before committing it.
More generally, I don't much like the approach to ctid-substitute
columns --- I think hacking on the rel's tupledesc like that is
guaranteed to break things all over the place. The assorted ugly
kluges that are already in the patch because of it are just scratching
the surface, and there may well be consequences that are flat out
unfixable. Probably the resjunk-columns mechanism would offer a better
solution.

Probably, the latest patch takes an approach that utilizes resjunk-columns
that moves remote row-identifier on scan stage to modify stage, but no
hacks on tupledesc.
The new GetForeignRelWidth API allows FDW drivers to append slots
to return a few pseudo-columns to upper level scan. It can contain
a remote row-identifier of the row to be modified.
Also, rewriteTargetListUD() injects a junk target-entry to reference this
pseudo-column on update or delete from foreign tables as regular
table is doing on ctid.

Regarding to the portion towards postgres_fdw, I'm under reworking
on the part-2 of this patch to apply cleanly.

Thanks,
--
KaiGai Kohei <kaigai@kaigai.gr.jp>

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#58Tom Lane
tgl@sss.pgh.pa.us
In reply to: Kohei KaiGai (#53)
Re: [v9.3] writable foreign tables

Kohei KaiGai <kaigai@kaigai.gr.jp> writes:

[ pgsql-v9.3-writable-fdw-poc.v12.part-1/2.patch ]

Applied after rather extensive editorialization. DELETE RETURNING in
particular was a mess, and I also tried to make SELECT FOR UPDATE behave
in what seemed like a sane fashion.

There's a lot left to do here of course. One thing I was wondering
about was why we don't allow DEFAULTs to be attached to foreign-table
columns. There was no use in it before, but it seems sensible enough
now.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#59Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tom Lane (#58)
postgres_fdw vs data formatting GUCs (was Re: [v9.3] writable foreign tables)

I wrote:

There's a lot left to do here of course. One thing I was wondering
about was why we don't allow DEFAULTs to be attached to foreign-table
columns. There was no use in it before, but it seems sensible enough
now.

Hmm ... the buildfarm just rubbed my nose in a more immediate issue,
which is that postgres_fdw is vulnerable to problems if the remote
server is using different GUC settings than it is for things like
timezone and datestyle. The failure seen here:
http://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=rover_firefly&amp;dt=2013-03-10%2018%3A30%3A00
is basically just cosmetic, but it's not hard to imagine non-cosmetic
problems coming up. For instance, suppose our instance is running in
DMY datestyle and transmits an ambiguous date to a remote running in
MDY datestyle.

We could consider sending our settings to the remote at connection
establishment, but that doesn't seem terribly bulletproof --- what if
someone does a local SET later? What seems safer is to set the remote
to ISO style always, but then we have to figure out how to get the local
timestamptz_out to emit that style without touching our local GUC.
Ugh.

(One more reason why GUCs that affect application-visible semantics are
dangerous.)

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#60Andrew Dunstan
andrew@dunslane.net
In reply to: Tom Lane (#58)
Re: [v9.3] writable foreign tables

On 03/10/2013 02:32 PM, Tom Lane wrote:

Kohei KaiGai <kaigai@kaigai.gr.jp> writes:

[ pgsql-v9.3-writable-fdw-poc.v12.part-1/2.patch ]

Applied after rather extensive editorialization. DELETE RETURNING in
particular was a mess, and I also tried to make SELECT FOR UPDATE behave
in what seemed like a sane fashion.

There's a lot left to do here of course. One thing I was wondering
about was why we don't allow DEFAULTs to be attached to foreign-table
columns. There was no use in it before, but it seems sensible enough
now.

Excellent news. But I noticed as I went to update my non-writeable FDW
that this has happened in the regression tests. Is this correct?

*** 
/home/pgl/npgl/fdw/file_text_array_fdw/expected/file_textarray_fdw.out 
2013-03-10 16:28:00.120340629 -0400
--- 
/home/pgl/npgl/fdw/file_text_array_fdw/results/file_textarray_fdw.out 
2013-03-10 16:28:00.595340910 -0400
***************
*** 188,196 ****
   LINE 1: DELETE FROM agg_csv_array WHERE a = 100;
                                           ^
   SELECT * FROM agg_csv_array FOR UPDATE OF agg_csv_array;
! ERROR:  SELECT FOR UPDATE/SHARE cannot be used with foreign table 
"agg_csv_array"
! LINE 1: SELECT * FROM agg_csv_array FOR UPDATE OF agg_csv_array;
!                                                   ^
   -- but this should be ignored
   SELECT * FROM agg_csv_array FOR UPDATE;
         t
--- 188,200 ----
   LINE 1: DELETE FROM agg_csv_array WHERE a = 100;
                                           ^
   SELECT * FROM agg_csv_array FOR UPDATE OF agg_csv_array;
!       t
! --------------
!  {100,99.097}
!  {0,0.09561}
!  {42,324.78}
! (3 rows)
!
   -- but this should be ignored
   SELECT * FROM agg_csv_array FOR UPDATE;
         t

cheers

andrew

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#61Thom Brown
thom@linux.com
In reply to: Tom Lane (#58)
Re: [v9.3] writable foreign tables

On 10 March 2013 18:32, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Kohei KaiGai <kaigai@kaigai.gr.jp> writes:

[ pgsql-v9.3-writable-fdw-poc.v12.part-1/2.patch ]

Applied after rather extensive editorialization. DELETE RETURNING in
particular was a mess, and I also tried to make SELECT FOR UPDATE behave
in what seemed like a sane fashion.

There's a lot left to do here of course. One thing I was wondering
about was why we don't allow DEFAULTs to be attached to foreign-table
columns. There was no use in it before, but it seems sensible enough
now.

Yes...

postgres=# INSERT INTO animals (id, animal, age) VALUES (DEFAULT,
'okapi', NULL);
ERROR: null value in column "id" violates not-null constraint
DETAIL: Failing row contains (null, okapi, null).
CONTEXT: Remote SQL command: INSERT INTO public.animals(id, animal,
age) VALUES ($1, $2, $3)

Out of curiosity, is there any way to explicitly force a foreign
DEFAULT with column-omission?

--
Thom

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#62Thom Brown
thom@linux.com
In reply to: Thom Brown (#61)
Re: [v9.3] writable foreign tables

On 10 March 2013 20:38, Thom Brown <thom@linux.com> wrote:

On 10 March 2013 18:32, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Kohei KaiGai <kaigai@kaigai.gr.jp> writes:

[ pgsql-v9.3-writable-fdw-poc.v12.part-1/2.patch ]

Applied after rather extensive editorialization. DELETE RETURNING in
particular was a mess, and I also tried to make SELECT FOR UPDATE behave
in what seemed like a sane fashion.

There's a lot left to do here of course. One thing I was wondering
about was why we don't allow DEFAULTs to be attached to foreign-table
columns. There was no use in it before, but it seems sensible enough
now.

Yes...

postgres=# INSERT INTO animals (id, animal, age) VALUES (DEFAULT,
'okapi', NULL);
ERROR: null value in column "id" violates not-null constraint
DETAIL: Failing row contains (null, okapi, null).
CONTEXT: Remote SQL command: INSERT INTO public.animals(id, animal,
age) VALUES ($1, $2, $3)

Out of curiosity, is there any way to explicitly force a foreign
DEFAULT with column-omission?

Looks like we'll also need tab-completion for UPDATE, INSERT and
DELETE statements on foreign tables.

--
Thom

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#63Tom Lane
tgl@sss.pgh.pa.us
In reply to: Andrew Dunstan (#60)
Re: [v9.3] writable foreign tables

Andrew Dunstan <andrew@dunslane.net> writes:

Excellent news. But I noticed as I went to update my non-writeable FDW
that this has happened in the regression tests. Is this correct?

Yeah, see the adjustment I made in the file_fdw test (which that looks
to be borrowed from).

The new theory is that SELECT FOR UPDATE is allowed on foreign tables,
and if the FDW doesn't do anything to implement it, it's just a no-op.

I looked into having the core code throw an error, but it was a pain
in the rear and of dubious merit anyway (since you can't really tell
for sure from outside the FDW whether the FDW did anything or whether
there's even any need to do anything for the particular data source).
Besides, the old behavior was less than consistent, since it only
complained when the FOR UPDATE directly mentioned the foreign table,
though that's not what the semantics are supposed to be.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#64Tom Lane
tgl@sss.pgh.pa.us
In reply to: Thom Brown (#61)
Re: [v9.3] writable foreign tables

Thom Brown <thom@linux.com> writes:

On 10 March 2013 18:32, Tom Lane <tgl@sss.pgh.pa.us> wrote:

There's a lot left to do here of course. One thing I was wondering
about was why we don't allow DEFAULTs to be attached to foreign-table
columns. There was no use in it before, but it seems sensible enough
now.

postgres=# INSERT INTO animals (id, animal, age) VALUES (DEFAULT,
'okapi', NULL);
ERROR: null value in column "id" violates not-null constraint
DETAIL: Failing row contains (null, okapi, null).

Out of curiosity, is there any way to explicitly force a foreign
DEFAULT with column-omission?

That's one of the things that would have to be worked out before
we could implement anything here. The easy answer would be that DEFAULT
specifies the local default, and only if you omit the column entirely
from the local command (including not having a local default) does the
remote default take effect. But whether that would be convenient to
use is hard to tell.

Another thing that would be easy to implement is to say that the new row
value is fully determined locally (including defaults if any) and remote
defaults have nothing to do with it. But I think that's almost
certainly a usability fail --- imagine that the remote has a
sequence-generated primary key, for instance. I think it's probably
necessary to permit remote insertion of defaults for that sort of table
definition to work conveniently.

Not real sure what the ideal behavior would be or how hard it would be
to implement.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#65Daniel Farina
daniel@heroku.com
In reply to: Tom Lane (#59)
Re: postgres_fdw vs data formatting GUCs (was Re: [v9.3] writable foreign tables)

On Sun, Mar 10, 2013 at 12:15 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

I wrote:

There's a lot left to do here of course. One thing I was wondering
about was why we don't allow DEFAULTs to be attached to foreign-table
columns. There was no use in it before, but it seems sensible enough
now.

Hmm ... the buildfarm just rubbed my nose in a more immediate issue,
which is that postgres_fdw is vulnerable to problems if the remote
server is using different GUC settings than it is for things like
timezone and datestyle. The failure seen here:
http://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=rover_firefly&amp;dt=2013-03-10%2018%3A30%3A00
is basically just cosmetic, but it's not hard to imagine non-cosmetic
problems coming up. For instance, suppose our instance is running in
DMY datestyle and transmits an ambiguous date to a remote running in
MDY datestyle.

We could consider sending our settings to the remote at connection
establishment, but that doesn't seem terribly bulletproof --- what if
someone does a local SET later? What seems safer is to set the remote
to ISO style always, but then we have to figure out how to get the local
timestamptz_out to emit that style without touching our local GUC.
Ugh.

Forgive my naivety: why would a timestamptz value not be passed
through the _in/_out function locally at least once (hence, respecting
local GUCs) when using the FDW? Is the problem overhead in not
wanting to parse and re-format the value on the local server?

Although it seems that if GUCs affected *parsing* then the problem
comes back again, since one may choose to canonicalize output from a
remote server (e.g. via setting ISO time in all cases) but should the
user have set up GUCs to interpret input in a particular way for a
data type that is not enough.

As-is this situation is already technically true for timestamptz in
the case of time stamps without any explicit time offset or time zone,
but since timestamptz_out doesn't ever render without the offset
(right?) it's not a problem. Close shave, though, and one that an
extension author could easily find themselves on the wrong side of.

I suppose that means any non-immutable in/out function pair may have
to be carefully considered, and the list is despairingly long...but
finite:

SELECT proname
FROM pg_proc WHERE EXISTS
(SELECT 1
FROM pg_type
WHERE pg_proc.oid = pg_type.typinput OR
pg_proc.oid = pg_type.typoutput OR
pg_proc.oid = pg_type.typsend OR
pg_proc.oid = pg_type.typreceive)
AND provolatile != 'i';

(One more reason why GUCs that affect application-visible semantics are

dangerous.)

:(

--
fdr

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#66Tom Lane
tgl@sss.pgh.pa.us
In reply to: Daniel Farina (#65)
Re: postgres_fdw vs data formatting GUCs (was Re: [v9.3] writable foreign tables)

Daniel Farina <daniel@heroku.com> writes:

On Sun, Mar 10, 2013 at 12:15 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Hmm ... the buildfarm just rubbed my nose in a more immediate issue,
which is that postgres_fdw is vulnerable to problems if the remote
server is using different GUC settings than it is for things like
timezone and datestyle.

Forgive my naivety: why would a timestamptz value not be passed
through the _in/_out function locally at least once (hence, respecting
local GUCs) when using the FDW? Is the problem overhead in not
wanting to parse and re-format the value on the local server?

No, the problem is that ambiguous dates may be transferred incorrectly
to or from the remote server. Once a timestamp value is inside our
server, we are responsible for getting it to the remote end accurately,
IMO.

Here's an example using the "loopback" server that's set up by the
postgres_fdw regression test:

contrib_regression=# show datestyle; -- this is the style the "remote" session will be using
DateStyle
-----------
ISO, MDY
(1 row)

contrib_regression=# create table remote (f1 timestamptz);
CREATE TABLE
contrib_regression=# create foreign table ft (f1 timestamptz) server loopback options (table_name 'remote');
CREATE FOREIGN TABLE
contrib_regression=# set datestyle = german, dmy;
SET
contrib_regression=# select now();
now
--------------------------------
11.03.2013 09:40:17.401173 EDT
(1 row)

contrib_regression=# insert into ft values(now());
INSERT 0 1
contrib_regression=# select *, now() from ft;
f1 | now
--------------------------------+-------------------------------
03.11.2013 08:40:58.682679 EST | 11.03.2013 09:41:30.96724 EDT
(1 row)

The remote end has entirely misinterpreted the day vs month fields.
Now, to hit this you need to be using a datestyle which will print
in an ambiguous format, so the "ISO" and "Postgres" styles are
not vulnerable; but "German" and "SQL" are.

I suppose that means any non-immutable in/out function pair may have
to be carefully considered, and the list is despairingly long...but
finite:

A look at pg_dump says that it only worries about setting datestyle,
intervalstyle, and extra_float_digits.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#67Josh Berkus
josh@agliodbs.com
In reply to: Tom Lane (#66)
Re: postgres_fdw vs data formatting GUCs (was Re: [v9.3] writable foreign tables)

The remote end has entirely misinterpreted the day vs month fields.
Now, to hit this you need to be using a datestyle which will print
in an ambiguous format, so the "ISO" and "Postgres" styles are
not vulnerable; but "German" and "SQL" are.

Is the "timezone" GUC a problem, also, for this? Seems like that could
be much worse ...

--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#68Tom Lane
tgl@sss.pgh.pa.us
In reply to: Josh Berkus (#67)
Re: postgres_fdw vs data formatting GUCs (was Re: [v9.3] writable foreign tables)

Josh Berkus <josh@agliodbs.com> writes:

The remote end has entirely misinterpreted the day vs month fields.
Now, to hit this you need to be using a datestyle which will print
in an ambiguous format, so the "ISO" and "Postgres" styles are
not vulnerable; but "German" and "SQL" are.

Is the "timezone" GUC a problem, also, for this? Seems like that could
be much worse ...

I'm not sure why you think being off by an hour is "much worse" than
being off by nine months ;-). But anyway, timezone seems to be mostly a
cosmetic issue, since timestamptz values will always get printed with an
explicit zone value. I think you could possibly burn yourself if a
foreign table were declared as being type timestamp when the underlying
column is really timestamptz ... but that kind of misconfiguration would
probably create a lot of misbehaviors above and beyond this one.

Having said that, I'd still be inclined to try to set the remote's
timezone GUC just so that error messages coming back from the remote
don't reflect a randomly different timezone, which was the basic issue
in the buildfarm failures we saw yesterday. OTOH, there is no guarantee
at all that the remote has the same timezone database we do, so it may
not know the zone or may think it has different DST rules than we think;
so it's not clear how far we can get with that. Maybe we should just
set the remote session's timezone to GMT always.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#69Josh Berkus
josh@agliodbs.com
In reply to: Tom Lane (#68)
Re: postgres_fdw vs data formatting GUCs (was Re: [v9.3] writable foreign tables)

Having said that, I'd still be inclined to try to set the remote's
timezone GUC just so that error messages coming back from the remote
don't reflect a randomly different timezone, which was the basic issue
in the buildfarm failures we saw yesterday. OTOH, there is no guarantee
at all that the remote has the same timezone database we do, so it may
not know the zone or may think it has different DST rules than we think;
so it's not clear how far we can get with that. Maybe we should just
set the remote session's timezone to GMT always.

Yeah, that seems the safest choice. What are the potential drawbacks,
if any?

--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#70Tom Lane
tgl@sss.pgh.pa.us
In reply to: Tom Lane (#64)
Column defaults for foreign tables (was Re: [v9.3] writable foreign tables)

I wrote:

Thom Brown <thom@linux.com> writes:

Out of curiosity, is there any way to explicitly force a foreign
DEFAULT with column-omission?

That's one of the things that would have to be worked out before
we could implement anything here. The easy answer would be that DEFAULT
specifies the local default, and only if you omit the column entirely
from the local command (including not having a local default) does the
remote default take effect. But whether that would be convenient to
use is hard to tell.

Another thing that would be easy to implement is to say that the new row
value is fully determined locally (including defaults if any) and remote
defaults have nothing to do with it. But I think that's almost
certainly a usability fail --- imagine that the remote has a
sequence-generated primary key, for instance. I think it's probably
necessary to permit remote insertion of defaults for that sort of table
definition to work conveniently.

I looked into this a bit, and realize that the code-as-committed is
already not self-consistent, because these give different results:

insert into foreigntable default values;

insert into foreigntable values(default, default, ...);

The former case inserts whatever the remote-side default values are.
The latter case inserts NULLs, regardless of the remote defaults,
because that's what the query is expanded to by the rewriter. So it
seems like this is something we must fix for 9.3, because otherwise
we're going to have backwards-compatibility issues if we try to change
the behavior later.

I've concluded that the "ideal behavior" probably is that if you have
declared a DEFAULT expression for a foreign table's column, then that's
what the default is for the purpose of inserts or updates through the
foreign table; but if you haven't, then (at least for postgres_fdw)
the effective default is whatever the remote table has.

I thought at first that we could fix this, and the related case

update foreigntable set somecolumn = default

with some relatively localized hacking in the rewriter. However, that
idea fell down when I looked at multi-row inserts:

insert into foreigntable
values (x, y, z), (a, default, b), (c, d, default), ...

The current implementation of this requires substituting the appropriate
column default expressions into the VALUES lists at rewrite time.
That's okay for a default expression that is known locally and should be
evaluated locally; but I see absolutely no practical way to make it work
if we'd like to have the defaults inserted remotely. We'd need to have
some out-of-band indication that a row being returned by the ValuesScan
node had had "default" placeholders in particular columns --- and I just
can't see us doing the amount of violence that would need to be done to
the executor to make that happen. Especially not in the 9.3 timeframe.

So one possible answer is to adopt the ignore-remote-defaults semantics
I suggested above, but I don't much like that from a usability standpoint.

Another idea is to throw a "not implemented" error on the specific case
of a multi-row VALUES with DEFAULT placeholders when the target is a
foreign table. That's pretty grotty, not least because it would have to
reject the case for all foreign tables not just postgres_fdw ones. But
it would give us some wiggle room to implement more desirable semantics
in the cases that we can handle reasonably.

Thoughts?

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#71Greg Stark
stark@mit.edu
In reply to: Tom Lane (#64)
Re: [v9.3] writable foreign tables

On Sun, Mar 10, 2013 at 10:01 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Another thing that would be easy to implement is to say that the new row
value is fully determined locally (including defaults if any) and remote
defaults have nothing to do with it. But I think that's almost
certainly a usability fail --- imagine that the remote has a
sequence-generated primary key, for instance. I think it's probably
necessary to permit remote insertion of defaults for that sort of table
definition to work conveniently.

It feels a bit like unpredictable magic to have "DEFAULT" mean one
thing and omitted columns mean something else. Perhaps we should have
an explicit LOCAL DEFAULT and REMOTE DEFAULT and then have DEFAULT and
omitted columns both mean the same thing.

This starts getting a bit weird if you start to ask what happens when
the remote table is itself an FDW though....

--
greg

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#72Tom Lane
tgl@sss.pgh.pa.us
In reply to: Greg Stark (#71)
Re: [v9.3] writable foreign tables

Greg Stark <stark@mit.edu> writes:

It feels a bit like unpredictable magic to have "DEFAULT" mean one
thing and omitted columns mean something else.

Agreed. The current code behaves that way, but I think that's
indisputably a bug not behavior we want to keep.

Perhaps we should have
an explicit LOCAL DEFAULT and REMOTE DEFAULT and then have DEFAULT and
omitted columns both mean the same thing.

I don't think we really want to introduce new syntax for this, do you?
Especially not when many FDWs won't have a notion of a remote default
at all.

My thought was that the ideal behavior is that there's only one default
for a column, with any local definition of it taking precedence over any
remote definition. But see later message about how that may be hard to
implement correctly.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#73Thom Brown
thom@linux.com
In reply to: Greg Stark (#71)
Re: [v9.3] writable foreign tables

On 11 March 2013 19:00, Greg Stark <stark@mit.edu> wrote:

On Sun, Mar 10, 2013 at 10:01 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Another thing that would be easy to implement is to say that the new row
value is fully determined locally (including defaults if any) and remote
defaults have nothing to do with it. But I think that's almost
certainly a usability fail --- imagine that the remote has a
sequence-generated primary key, for instance. I think it's probably
necessary to permit remote insertion of defaults for that sort of table
definition to work conveniently.

It feels a bit like unpredictable magic to have "DEFAULT" mean one
thing and omitted columns mean something else. Perhaps we should have
an explicit LOCAL DEFAULT and REMOTE DEFAULT and then have DEFAULT and
omitted columns both mean the same thing.

This starts getting a bit weird if you start to ask what happens when
the remote table is itself an FDW though....

We could have something like:

CREATE FOREIGN TABLE ...
... OPTION (default <locality>);

where <locality> is 'local' or 'remote'. But then this means it still
can't be specified in individual queries, or have a different locality
between columns on the same foreign table.

--
Thom

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#74Tom Lane
tgl@sss.pgh.pa.us
In reply to: Josh Berkus (#69)
Re: postgres_fdw vs data formatting GUCs (was Re: [v9.3] writable foreign tables)

Josh Berkus <josh@agliodbs.com> writes:

Having said that, I'd still be inclined to try to set the remote's
timezone GUC just so that error messages coming back from the remote
don't reflect a randomly different timezone, which was the basic issue
in the buildfarm failures we saw yesterday. OTOH, there is no guarantee
at all that the remote has the same timezone database we do, so it may
not know the zone or may think it has different DST rules than we think;
so it's not clear how far we can get with that. Maybe we should just
set the remote session's timezone to GMT always.

Yeah, that seems the safest choice. What are the potential drawbacks,
if any?

Hard to tell if there are any without testing it.

I remembered that there's a relatively inexpensive way to set GUC values
transiently within an operation, which is GUC_ACTION_SAVE; both
extension.c and ri_triggers.c are relying on that. So here's my
proposal for a fix:

* To make the remote end transmit values unambiguously, send SET
commands for the GUCs listed below during remote session setup.
(postgres_fdw is already assuming that such SETs will persist for the
whole session.)

* To make our end transmit values unambiguously, use GUC_ACTION_SAVE to
transiently change the GUCs listed below whenever we are converting
values to text form to send to the remote end. (This would include
deparsing of Const nodes as well as transmission of query parameters.)

* Judging from the precedent of pg_dump, these are the things we ought
to set this way:

DATESTYLE = ISO

INTERVALSTYLE = POSTGRES (skip on remote side, if version < 8.4)

EXTRA_FLOAT_DIGITS = 3 (or 2 on remote side, if version < 9.0)

* In addition I propose we set TIMEZONE = UTC on the remote side only.
This is, I believe, just a cosmetic hack so that timestamptz values
coming back in error messages will be printed consistently; it would let
us revert the kluge solution I put in place for this type of regression
failure:
http://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=rover_firefly&amp;dt=2013-03-10%2018%3A30%3A00

BTW, it strikes me that dblink is probably subject to at least some of
these same failure modes. I'm not personally volunteering to fix any
of this in dblink, but maybe someone ought to look into that.

Thoughts?

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#75Daniel Farina
daniel@heroku.com
In reply to: Tom Lane (#74)
Re: postgres_fdw vs data formatting GUCs (was Re: [v9.3] writable foreign tables)

On Mon, Mar 11, 2013 at 12:30 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

BTW, it strikes me that dblink is probably subject to at least some of
these same failure modes. I'm not personally volunteering to fix any
of this in dblink, but maybe someone ought to look into that.

I will try to make time for this, although it seems like the general
approach should match pgsql_fdw if possible. Is the current thinking
to forward the settings and then use the GUC hooks to track updates?

--
fdr

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#76Tom Lane
tgl@sss.pgh.pa.us
In reply to: Daniel Farina (#75)
Re: postgres_fdw vs data formatting GUCs (was Re: [v9.3] writable foreign tables)

Daniel Farina <daniel@heroku.com> writes:

I will try to make time for this, although it seems like the general
approach should match pgsql_fdw if possible. Is the current thinking
to forward the settings and then use the GUC hooks to track updates?

That's not what I had in mind for postgres_fdw --- rather the idea is to
avoid needing on-the-fly changes in remote-side settings, because those
are so expensive to make. However, postgres_fdw is fortunate in that
the SQL it expects to execute on the remote side is very constrained.
dblink might need a different solution that would leave room for
user-driven changes of remote-side settings.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#77Albe Laurenz
laurenz.albe@wien.gv.at
In reply to: Tom Lane (#70)
Re: Column defaults for foreign tables (was Re: [v9.3] writable foreign tables)

Tom Lane wrote:

Thom Brown <thom@linux.com> writes:

Out of curiosity, is there any way to explicitly force a foreign
DEFAULT with column-omission?

I've concluded that the "ideal behavior" probably is that if you have
declared a DEFAULT expression for a foreign table's column, then that's
what the default is for the purpose of inserts or updates through the
foreign table; but if you haven't, then (at least for postgres_fdw)
the effective default is whatever the remote table has.

I agree.

I thought at first that we could fix this, and the related case

update foreigntable set somecolumn = default

with some relatively localized hacking in the rewriter. However, that
idea fell down when I looked at multi-row inserts:

insert into foreigntable
values (x, y, z), (a, default, b), (c, d, default), ...

The current implementation of this requires substituting the appropriate
column default expressions into the VALUES lists at rewrite time.
That's okay for a default expression that is known locally and should be
evaluated locally; but I see absolutely no practical way to make it work
if we'd like to have the defaults inserted remotely. We'd need to have
some out-of-band indication that a row being returned by the ValuesScan
node had had "default" placeholders in particular columns --- and I just
can't see us doing the amount of violence that would need to be done to
the executor to make that happen. Especially not in the 9.3 timeframe.

So one possible answer is to adopt the ignore-remote-defaults semantics
I suggested above, but I don't much like that from a usability standpoint.

Another idea is to throw a "not implemented" error on the specific case
of a multi-row VALUES with DEFAULT placeholders when the target is a
foreign table. That's pretty grotty, not least because it would have to
reject the case for all foreign tables not just postgres_fdw ones. But
it would give us some wiggle room to implement more desirable semantics
in the cases that we can handle reasonably.

Thoughts?

Do you think that it is possible to insert remote defaults
by omitting columns like this:

INSERT INTO foreigntable (col1, col3) VALUES (a, c);

If that can be made to work, then my opinion is that throwing an
error on

INSERT INTO foreigntable (col1, col2, col3) VALUES (a, DEFAULT, c);

would be acceptable, because there is at least a workaround.

If the first variant also cannot be made to work with remote
defaults, then I'd say that the best is to use local
defaults throughout and accept the loss of usability.

Yours,
Laurenz Albe

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#78Daniel Farina
daniel@heroku.com
In reply to: Tom Lane (#76)
Re: postgres_fdw vs data formatting GUCs (was Re: [v9.3] writable foreign tables)

On Mon, Mar 11, 2013 at 7:04 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Daniel Farina <daniel@heroku.com> writes:

I will try to make time for this, although it seems like the general
approach should match pgsql_fdw if possible. Is the current thinking
to forward the settings and then use the GUC hooks to track updates?

That's not what I had in mind for postgres_fdw --- rather the idea is to
avoid needing on-the-fly changes in remote-side settings, because those
are so expensive to make. However, postgres_fdw is fortunate in that
the SQL it expects to execute on the remote side is very constrained.
dblink might need a different solution that would leave room for
user-driven changes of remote-side settings.

Okay, I see. So inverting the thinking I wrote earlier: how about
hearkening carefully to any ParameterStatus messages on the local side
before entering the inner loop of dblink.c:materializeResult as to set
the local GUC (and carefully dropping it back off after
materializeResult) so that the the _in functions can evaluate the
input in the same relevant GUC context as the remote side?

That should handle SET actions executed remotely.

Otherwise it seems like a solution would have to be ambitious enough
to encompass reifying the GUCs from the afflicted parsers, which I
surmise is not something that we want to treat right now.

--
fdr

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#79Tom Lane
tgl@sss.pgh.pa.us
In reply to: Daniel Farina (#78)
Re: postgres_fdw vs data formatting GUCs (was Re: [v9.3] writable foreign tables)

Daniel Farina <daniel@heroku.com> writes:

Okay, I see. So inverting the thinking I wrote earlier: how about
hearkening carefully to any ParameterStatus messages on the local side
before entering the inner loop of dblink.c:materializeResult as to set
the local GUC (and carefully dropping it back off after
materializeResult) so that the the _in functions can evaluate the
input in the same relevant GUC context as the remote side?

Yeah, watching the remote side's datestyle and intervalstyle and
matching them (for both input and output) would probably work.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#80Tom Lane
tgl@sss.pgh.pa.us
In reply to: Albe Laurenz (#77)
Re: Column defaults for foreign tables (was Re: [v9.3] writable foreign tables)

Albe Laurenz <laurenz.albe@wien.gv.at> writes:

Do you think that it is possible to insert remote defaults
by omitting columns like this:
INSERT INTO foreigntable (col1, col3) VALUES (a, c);

Well, that's how it works right now, but it's not good that it's
inconsistent with the explicit-DEFAULT case.

If that can be made to work, then my opinion is that throwing an
error on
INSERT INTO foreigntable (col1, col2, col3) VALUES (a, DEFAULT, c);
would be acceptable, because there is at least a workaround.

Aside from the oddness of not supporting that when it's equivalent
to the other case, what about this:

UPDATE foreigntable SET col2 = DEFAULT WHERE ...

There is no simple workaround if we don't provide support for that.

If the first variant also cannot be made to work with remote
defaults, then I'd say that the best is to use local
defaults throughout and accept the loss of usability.

Yeah, I'm drifting towards the position that we should just define the
defaults as being whatever they are locally, rather than trying to be
cute about supporting remotely-executed defaults. It looks to me like
if we try to do the latter, we're going to have pitfalls and weird
corner cases that will never be quite transparent. There's also the
argument that this'd be a lot of work that benefits only some FDWs,
since the whole concept of remote column defaults doesn't apply when
the FDW's data source isn't a traditional RDBMS.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#81Albe Laurenz
laurenz.albe@wien.gv.at
In reply to: Tom Lane (#80)
Re: Column defaults for foreign tables (was Re: [v9.3] writable foreign tables)

Tom Lane wrote:

Yeah, I'm drifting towards the position that we should just define the
defaults as being whatever they are locally, rather than trying to be
cute about supporting remotely-executed defaults. It looks to me like
if we try to do the latter, we're going to have pitfalls and weird
corner cases that will never be quite transparent. There's also the
argument that this'd be a lot of work that benefits only some FDWs,
since the whole concept of remote column defaults doesn't apply when
the FDW's data source isn't a traditional RDBMS.

That was my first thought on the topic, to have a solution that
is simple (if not perfect).
Your argument that it would be unpleasant to lose the ability
to use sequence-generated remote default values made me reconsider.

But there is a workaround, namely to use a trigger before insert
to generate an automatic primary key (e.g. if the inserted value is
NULL).
Maybe it would be good to add a few hints at workarounds like that
to the documentation if it's going to be local defaults.

Yours,
Laurenz Albe

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#82Tom Lane
tgl@sss.pgh.pa.us
In reply to: Albe Laurenz (#81)
Re: Column defaults for foreign tables (was Re: [v9.3] writable foreign tables)

Albe Laurenz <laurenz.albe@wien.gv.at> writes:

Yeah, I'm drifting towards the position that we should just define the
defaults as being whatever they are locally, rather than trying to be
cute about supporting remotely-executed defaults.

That was my first thought on the topic, to have a solution that
is simple (if not perfect).
Your argument that it would be unpleasant to lose the ability
to use sequence-generated remote default values made me reconsider.

But there is a workaround, namely to use a trigger before insert
to generate an automatic primary key (e.g. if the inserted value is
NULL).

Another attack is to set up a different foreign table pointing to the
same remote table, but lacking the sequence column. When you insert via
that table, you'll get the remote's default for the hidden column.
This doesn't require any weird triggers on the remote side, but it could
be hard to persuade existing apps to use the second foreign table.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#83Thom Brown
thom@linux.com
In reply to: Tom Lane (#82)
Re: Column defaults for foreign tables (was Re: [v9.3] writable foreign tables)

On 13 March 2013 19:04, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Albe Laurenz <laurenz.albe@wien.gv.at> writes:

Yeah, I'm drifting towards the position that we should just define the
defaults as being whatever they are locally, rather than trying to be
cute about supporting remotely-executed defaults.

That was my first thought on the topic, to have a solution that
is simple (if not perfect).
Your argument that it would be unpleasant to lose the ability
to use sequence-generated remote default values made me reconsider.

But there is a workaround, namely to use a trigger before insert
to generate an automatic primary key (e.g. if the inserted value is
NULL).

Another attack is to set up a different foreign table pointing to the
same remote table, but lacking the sequence column. When you insert via
that table, you'll get the remote's default for the hidden column.
This doesn't require any weird triggers on the remote side, but it could
be hard to persuade existing apps to use the second foreign table.

How about:

CREATE FOREIGN TABLE tablename (id int DEFAULT PASSTHROUGH) SERVER pg_server;

That way it will pass DEFAULT through to the remote table as it's
defined on the table. Users can then explicitly insert values, or
select the default, which will configured to ensure the default on the
remote server is used... although I suspect I'm overlooking something
here.

--
Thom

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#84Tom Lane
tgl@sss.pgh.pa.us
In reply to: Thom Brown (#83)
Re: Column defaults for foreign tables (was Re: [v9.3] writable foreign tables)

Thom Brown <thom@linux.com> writes:

How about:

CREATE FOREIGN TABLE tablename (id int DEFAULT PASSTHROUGH) SERVER pg_server;

That way it will pass DEFAULT through to the remote table as it's
defined on the table. Users can then explicitly insert values, or
select the default, which will configured to ensure the default on the
remote server is used... although I suspect I'm overlooking something
here.

Yeah ... how to implement it.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#85Daniel Farina
daniel@heroku.com
In reply to: Tom Lane (#79)
Re: postgres_fdw vs data formatting GUCs (was Re: [v9.3] writable foreign tables)

On Tue, Mar 12, 2013 at 11:51 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Daniel Farina <daniel@heroku.com> writes:

Okay, I see. So inverting the thinking I wrote earlier: how about
hearkening carefully to any ParameterStatus messages on the local side
before entering the inner loop of dblink.c:materializeResult as to set
the local GUC (and carefully dropping it back off after
materializeResult) so that the the _in functions can evaluate the
input in the same relevant GUC context as the remote side?

Yeah, watching the remote side's datestyle and intervalstyle and
matching them (for both input and output) would probably work.

Alright, so I've been whacking at this and there's one interesting
thing to ask about: saving and restoring the local GUCs. There are a
bunch of things about GUCs besides their value that are maintained,
such as their 'source', so writing a little ad-hoc save/restore is not
going to do the right thing. Luckily, something much closer to the
right thing is done for SET LOCAL with transactions and
subtransactions, to push and pop GUC contexts:

guc.h:

extern int NewGUCNestLevel(void);
extern void AtEOXact_GUC(bool isCommit, int nestLevel);

By and large looking at the mechanics of these two functions, the
latter in particular has very little to do with the transaction
machinery in general. It's already being called from a bunch of
places that don't, to me, seem to really indicate a intrinsic
connection to transactions, e.g. do_analyze_rel, ri_triggers, and
postgres_fdw. I think in those cases the justification for settings
of 'isCommit' is informed by the mechanism more than the symbol name
or its comments. I count about ten call sites that are like this.

So, I can add one more such use of AtEOXact_GUC for the dblink fix,
but would it also be appropriate to find some new names for the
concepts (instead of AtEOXact_GUC and isCommit) here to more
accurately express what's going on?

I'll perhaps do that after finishing up the dblink fix if I receive
some positive response on the matter. However, if for some reason I
*shouldn't* use that machinery in dblink, I'd appreciate the guidance.

--
fdr

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#86Tom Lane
tgl@sss.pgh.pa.us
In reply to: Daniel Farina (#85)
Re: postgres_fdw vs data formatting GUCs (was Re: [v9.3] writable foreign tables)

Daniel Farina <daniel@heroku.com> writes:

On Tue, Mar 12, 2013 at 11:51 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Yeah, watching the remote side's datestyle and intervalstyle and
matching them (for both input and output) would probably work.

Alright, so I've been whacking at this and there's one interesting
thing to ask about: saving and restoring the local GUCs. There are a
bunch of things about GUCs besides their value that are maintained,
such as their 'source', so writing a little ad-hoc save/restore is not
going to do the right thing.

Right, you should use NewGUCNestLevel/AtEOXact_GUC. Look at the fixes
I committed in postgres_fdw a day or two ago for an example.

So, I can add one more such use of AtEOXact_GUC for the dblink fix,
but would it also be appropriate to find some new names for the
concepts (instead of AtEOXact_GUC and isCommit) here to more
accurately express what's going on?

Meh. I guess we could invent an "EndGUCNestLevel" that's a wrapper
around AtEOXact_GUC, but I'm not that excited about it ...

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#87Robert Haas
robertmhaas@gmail.com
In reply to: Tom Lane (#72)
Re: [v9.3] writable foreign tables

On Mon, Mar 11, 2013 at 3:06 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Greg Stark <stark@mit.edu> writes:

It feels a bit like unpredictable magic to have "DEFAULT" mean one
thing and omitted columns mean something else.

Agreed. The current code behaves that way, but I think that's
indisputably a bug not behavior we want to keep.

I'm not entirely convinced that's a bug. Both behaviors seem useful,
and there has to be some way to specify each one.

But I just work here.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#88Tom Lane
tgl@sss.pgh.pa.us
In reply to: Robert Haas (#87)
Re: [v9.3] writable foreign tables

Robert Haas <robertmhaas@gmail.com> writes:

On Mon, Mar 11, 2013 at 3:06 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Greg Stark <stark@mit.edu> writes:

It feels a bit like unpredictable magic to have "DEFAULT" mean one
thing and omitted columns mean something else.

Agreed. The current code behaves that way, but I think that's
indisputably a bug not behavior we want to keep.

I'm not entirely convinced that's a bug. Both behaviors seem useful,
and there has to be some way to specify each one.

I would love it if we had a way to provide remote-default
functionality. But per SQL spec these should produce the same results:
INSERT INTO t(f1, f2) VALUES(1, DEFAULT);
INSERT INTO t(f1) VALUES(1);
If PG fails to work like that, it's not a feature, it's a bug.
Where the default is coming from is not a justification for failing
the POLA like that.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#89Daniel Farina
daniel@heroku.com
In reply to: Tom Lane (#86)
1 attachment(s)
Re: postgres_fdw vs data formatting GUCs (was Re: [v9.3] writable foreign tables)

On Thu, Mar 14, 2013 at 8:07 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Daniel Farina <daniel@heroku.com> writes:

On Tue, Mar 12, 2013 at 11:51 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Yeah, watching the remote side's datestyle and intervalstyle and
matching them (for both input and output) would probably work.

Alright, so I've been whacking at this and there's one interesting
thing to ask about: saving and restoring the local GUCs. There are a
bunch of things about GUCs besides their value that are maintained,
such as their 'source', so writing a little ad-hoc save/restore is not
going to do the right thing.

Right, you should use NewGUCNestLevel/AtEOXact_GUC. Look at the fixes
I committed in postgres_fdw a day or two ago for an example.

Thanks. Here's a patch. Here is the comments on top of the patch file inlined:

Similar in purpose to cc3f281ffb0a5d9b187e7a7b7de4a045809ff683, but
taking into account that a dblink caller may choose to cause arbitrary
changes to DateStyle and IntervalStyle. To handle this, it is
necessary to use PQparameterStatus before parsing any input, every
time. This is avoided in cases that do not include the two
problematic types treated -- interval and timestamptz -- by scanning
the TupleDesc's types first.

Although it has been suggested that extra_float_digits would need
similar treatment as IntervalStyle and DateStyle (as it's seen in
pg_dump), extra_float_digits is not an GUC_REPORT-ed GUC and is not
able to be checked in PQparameterStatus, and furthermore, the float4
and float8 parsers are not sensitive to the GUC anyway: both accept as
much precision as is provided in an unambiguous way.

So, I can add one more such use of AtEOXact_GUC for the dblink fix,
but would it also be appropriate to find some new names for the
concepts (instead of AtEOXact_GUC and isCommit) here to more
accurately express what's going on?

Meh. I guess we could invent an "EndGUCNestLevel" that's a wrapper
around AtEOXact_GUC, but I'm not that excited about it ...

Per your sentiment, I won't pursue that then.

Overall, the patch I think has reasonably thorough regression testing
(I tried to hit the several code paths whereby one would have to scan
TupleDescs, as well as a few other edge cases) and has some of the
obvious optimizations in place: it doesn't call PQparameterStatus more
than once per vulnerable type per TupleDesc scan, and avoids using the
parameter status procedure at all if there are no affected types.

The mechanisms may be overwrought though, since it was originally
intended to generalize to three types before I realized that
extra_float_digits is another kind of problem entirely, leaving just
IntervalStyle and DateStyle remaining, which perhaps could have been
treated even more specially to make the patch more terse.

I'll add it to the commitfest.

--
fdr

Attachments:

dblink-guc-sensitive-types-v1.patchapplication/octet-stream; name=dblink-guc-sensitive-types-v1.patchDownload
Similar in purpose to cc3f281ffb0a5d9b187e7a7b7de4a045809ff683, but
taking into account that a dblink caller may choose to cause arbitrary
changes to DateStyle and IntervalStyle.  To handle this, it is
necessary to use PQparameterStatus before parsing any input, every
time.  This is avoided in cases that do not include the two
problematic types treated -- interval and timestamptz -- by scanning
the TupleDesc's types first.

Although it has been suggested that extra_float_digits would need
similar treatment as IntervalStyle and DateStyle (as it's seen in
pg_dump), extra_float_digits is not an GUC_REPORT-ed GUC and is not
able to be checked in PQparameterStatus, and furthermore, the float4
and float8 parsers are not sensitive to the GUC anyway: both accept as
much precision as is provided in an unambiguous way.
---
 contrib/dblink/dblink.c            |  225 +++++++++++++++++++++++++++++++++++-
 contrib/dblink/expected/dblink.out |  162 ++++++++++++++++++++++++++
 contrib/dblink/sql/dblink.sql      |   91 +++++++++++++++
 3 files changed, 472 insertions(+), 6 deletions(-)
*** a/contrib/dblink/dblink.c
--- b/contrib/dblink/dblink.c
***************
*** 53,58 ****
--- 53,59 ----
  #include "utils/acl.h"
  #include "utils/builtins.h"
  #include "utils/fmgroids.h"
+ #include "utils/guc.h"
  #include "utils/lsyscache.h"
  #include "utils/memutils.h"
  #include "utils/rel.h"
***************
*** 69,74 **** typedef struct remoteConn
--- 70,120 ----
  	bool		newXactForCursor;		/* Opened a transaction for a cursor */
  } remoteConn;
  
+ /*
+  * Used to index into an array to remember GUC info associated with
+  * types where input and output formatting may change the
+  * interpretation of the data.
+  */
+ typedef enum
+ {
+ 	GST_TIMESTAMPTZ = 0,
+ 	GST_INTERVAL,
+ 
+ 	/* GST_MAX must be the last element, for array sizing. */
+ 	GST_MAX
+ } gucSensitiveTypes;
+ 
+ /*
+  * remoteGucInfo saves the value of one GUC in connection with one
+  * type so that the GUC environment locally can match for correct type
+  * parsing.
+  */
+ typedef struct remoteGucInfo
+ {
+ 	bool		 isInitialized;
+ 	const char	*gucName;
+ 	const char	*remoteVal;
+ } remoteGucInfo;
+ 
+ /*
+  * Contains information to save and restore GUCs for types with
+  * GUC-sensitive parsing.
+  */
+ typedef struct remoteGucs
+ {
+ 	/*
+ 	 * GUC nesting level.  Set to -1 if no GUC nesting level has been
+ 	 * introduced.
+ 	 */
+ 	int localGUCNestLevel;
+ 
+ 	/* Kept around for PQparameterStatus to interrogate remote GUCs */
+ 	PGconn *conn;
+ 
+ 	/* Memory enough for each affected data type */
+ 	remoteGucInfo rgis[GST_MAX];
+ } remoteGucs;
+ 
  typedef struct storeInfo
  {
  	FunctionCallInfo fcinfo;
***************
*** 79,84 **** typedef struct storeInfo
--- 125,131 ----
  	/* temp storage for results to avoid leaks on exception */
  	PGresult   *last_res;
  	PGresult   *cur_res;
+ 	remoteGucs *rgs;
  } storeInfo;
  
  /*
***************
*** 86,92 **** typedef struct storeInfo
   */
  static Datum dblink_record_internal(FunctionCallInfo fcinfo, bool is_async);
  static void prepTuplestoreResult(FunctionCallInfo fcinfo);
! static void materializeResult(FunctionCallInfo fcinfo, PGresult *res);
  static void materializeQueryResult(FunctionCallInfo fcinfo,
  					   PGconn *conn,
  					   const char *conname,
--- 133,140 ----
   */
  static Datum dblink_record_internal(FunctionCallInfo fcinfo, bool is_async);
  static void prepTuplestoreResult(FunctionCallInfo fcinfo);
! static void materializeResult(FunctionCallInfo fcinfo, remoteGucs *rgs,
! 							  PGresult *res);
  static void materializeQueryResult(FunctionCallInfo fcinfo,
  					   PGconn *conn,
  					   const char *conname,
***************
*** 118,123 **** static void validate_pkattnums(Relation rel,
--- 166,174 ----
  				   int **pkattnums, int *pknumatts);
  static bool is_valid_dblink_option(const PQconninfoOption *options,
  					   const char *option, Oid context);
+ static void initRemoteGucs(remoteGucs *rgs, PGconn *conn);
+ static void applyRemoteGucs(remoteGucs *rgs, TupleDesc tupdesc);
+ static void restoreLocalGucs(remoteGucs *rgs);
  
  /* Global */
  static remoteConn *pconn = NULL;
***************
*** 531,536 **** dblink_fetch(PG_FUNCTION_ARGS)
--- 582,588 ----
  	char	   *curname = NULL;
  	int			howmany = 0;
  	bool		fail = true;	/* default to backward compatible */
+ 	remoteGucs	rgs;
  
  	prepTuplestoreResult(fcinfo);
  
***************
*** 605,611 **** dblink_fetch(PG_FUNCTION_ARGS)
  				 errmsg("cursor \"%s\" does not exist", curname)));
  	}
  
! 	materializeResult(fcinfo, res);
  	return (Datum) 0;
  }
  
--- 657,665 ----
  				 errmsg("cursor \"%s\" does not exist", curname)));
  	}
  
! 	initRemoteGucs(&rgs, conn);
! 	materializeResult(fcinfo, &rgs, res);
! 
  	return (Datum) 0;
  }
  
***************
*** 656,663 **** dblink_get_result(PG_FUNCTION_ARGS)
  static Datum
  dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
  {
! 	PGconn	   *volatile conn = NULL;
! 	volatile bool freeconn = false;
  
  	prepTuplestoreResult(fcinfo);
  
--- 710,718 ----
  static Datum
  dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
  {
! 	PGconn	   *volatile		conn	 = NULL;
! 	volatile bool				freeconn = false;
! 	remoteGucs					rgs;
  
  	prepTuplestoreResult(fcinfo);
  
***************
*** 750,756 **** dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
  				}
  				else
  				{
! 					materializeResult(fcinfo, res);
  				}
  			}
  		}
--- 805,812 ----
  				}
  				else
  				{
! 					initRemoteGucs(&rgs, conn);
! 					materializeResult(fcinfo, &rgs, res);
  				}
  			}
  		}
***************
*** 803,812 **** prepTuplestoreResult(FunctionCallInfo fcinfo)
  /*
   * Copy the contents of the PGresult into a tuplestore to be returned
   * as the result of the current function.
   * The PGresult will be released in this function.
   */
  static void
! materializeResult(FunctionCallInfo fcinfo, PGresult *res)
  {
  	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
  
--- 859,869 ----
  /*
   * Copy the contents of the PGresult into a tuplestore to be returned
   * as the result of the current function.
+  *
   * The PGresult will be released in this function.
   */
  static void
! materializeResult(FunctionCallInfo fcinfo, remoteGucs *rgs, PGresult *res)
  {
  	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
  
***************
*** 891,896 **** materializeResult(FunctionCallInfo fcinfo, PGresult *res)
--- 948,956 ----
  			rsinfo->setDesc = tupdesc;
  			MemoryContextSwitchTo(oldcontext);
  
+ 			/* Check types in return tupdesc for GUC sensitivity */
+ 			applyRemoteGucs(rgs, tupdesc);
+ 
  			values = (char **) palloc(nfields * sizeof(char *));
  
  			/* put all tuples into the tuplestore */
***************
*** 930,938 **** materializeResult(FunctionCallInfo fcinfo, PGresult *res)
--- 990,1004 ----
  	{
  		/* be sure to release the libpq result */
  		PQclear(res);
+ 
+ 		/* Pop any set GUCs, if necessary */
+ 		restoreLocalGucs(rgs);
  		PG_RE_THROW();
  	}
  	PG_END_TRY();
+ 
+ 	/* Pop any set GUCs, if necessary */
+ 	restoreLocalGucs(rgs);
  }
  
  /*
***************
*** 953,958 **** materializeQueryResult(FunctionCallInfo fcinfo,
--- 1019,1027 ----
  	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
  	PGresult   *volatile res = NULL;
  	storeInfo	sinfo;
+ 	remoteGucs	rgs;
+ 
+ 	initRemoteGucs(&rgs, conn);
  
  	/* prepTuplestoreResult must have been called previously */
  	Assert(rsinfo->returnMode == SFRM_Materialize);
***************
*** 960,965 **** materializeQueryResult(FunctionCallInfo fcinfo,
--- 1029,1035 ----
  	/* initialize storeInfo to empty */
  	memset(&sinfo, 0, sizeof(sinfo));
  	sinfo.fcinfo = fcinfo;
+ 	sinfo.rgs = &rgs;
  
  	PG_TRY();
  	{
***************
*** 1041,1049 **** materializeQueryResult(FunctionCallInfo fcinfo,
--- 1111,1126 ----
  		/* and clear out any pending data in libpq */
  		while ((res = PQgetResult(conn)) != NULL)
  			PQclear(res);
+ 
+ 		/* Pop any set GUCs, if necessary */
+ 		restoreLocalGucs(&rgs);
+ 
  		PG_RE_THROW();
  	}
  	PG_END_TRY();
+ 
+ 	/* Pop any set GUCs, if necessary */
+ 	restoreLocalGucs(&rgs);
  }
  
  /*
***************
*** 1186,1191 **** storeRow(storeInfo *sinfo, PGresult *res, bool first)
--- 1263,1277 ----
  									  ALLOCSET_DEFAULT_MINSIZE,
  									  ALLOCSET_DEFAULT_INITSIZE,
  									  ALLOCSET_DEFAULT_MAXSIZE);
+ 
+ 
+ 		/*
+ 		 * Apply any necessary GUCs to parse input properly.
+ 		 *
+ 		 * NB: This application of GUCs is un-done in callers, which in
+ 		 * particular have PG_TRY/PG_CATCH set up.
+ 		 */
+ 		applyRemoteGucs(sinfo->rgs, tupdesc);
  	}
  
  	/* Should have a single-row result if we get here */
***************
*** 2898,2900 **** is_valid_dblink_option(const PQconninfoOption *options, const char *option,
--- 2984,3113 ----
  
  	return true;
  }
+ 
+ /* Initializer for a "remoteGucs" struct value. */
+ static void
+ initRemoteGucs(remoteGucs *rgs, PGconn *conn)
+ {
+ 	rgs->localGUCNestLevel = -1;
+ 	rgs->conn			   = conn;
+ 
+ 	MemSet(&rgs->rgis, 0, sizeof rgs->rgis);
+ }
+ 
+ /*
+  * Scan a TupleDesc and, should it contain types that are sensitive to
+  * GUCs, acquire remote GUCs and set them in a new GUC nesting level.
+  * This is undone with restoreLocalGucs.
+  */
+ static void
+ applyRemoteGucs(remoteGucs *rgs, TupleDesc tupdesc)
+ {
+ 	int			field;
+ 	int			gstCur;
+ 	bool		anyInitialized = false;
+ 
+ 	for (field = 0; field < tupdesc->natts; field += 1)
+ 	{
+ 		const Oid typ = tupdesc->attrs[field]->atttypid;
+ 		remoteGucInfo *gi;
+ 
+ 		switch (typ)
+ 		{
+ 			case TIMESTAMPTZOID:
+ 				gi = &rgs->rgis[GST_TIMESTAMPTZ];
+ 				gi->gucName = "DateStyle";
+ 				break;
+ 
+ 			case INTERVALOID:
+ 				gi = &rgs->rgis[GST_INTERVAL];
+ 				gi->gucName = "IntervalStyle";
+ 				break;
+ 
+ 			default:
+ 				/*
+ 				 * Not an interesting type: keep looking for another.
+ 				 */
+ 				continue;
+ 		}
+ 
+ 		if (gi->isInitialized)
+ 		{
+ 			/*
+ 			 * If the affected type has been seen twice in the tupdesc
+ 			 * (e.g. two timestamptz attributes), the necessary work
+ 			 * has already been done in a previous iteration.
+ 			 */
+ 			continue;
+ 		}
+ 
+ 		/*
+ 		 * Looks like this is a type that needs GUC saving.  Assert some
+ 		 * invariants before going through with it.
+ 		 */
+ 		Assert(gi != NULL);
+ 		Assert(!gi->isInitialized);
+ 		Assert(gi->gucName != NULL);
+ 
+ 		/* Save the remote GUC */
+ 		gi->remoteVal = PQparameterStatus(rgs->conn, gi->gucName);
+ 
+ 		gi->isInitialized = true;
+ 		anyInitialized = true;
+ 	}
+ 
+ 	/*
+ 	 * Exit immediately without applying any GUCs if no types seen in
+ 	 * the tupdesc are affected.
+ 	 */
+ 	if (!anyInitialized)
+ 		return;
+ 
+ 	/*
+ 	 * Affected types require local GUC manipulations.  Create a new
+ 	 * GUC NestLevel to overlay the remote settings.
+ 	 *
+ 	 * Also, this nesting is done exactly once per remoteGucInfo
+ 	 * structure, so expect it to come with an invalid NestLevel.
+ 	 */
+ 	Assert(rgs->localGUCNestLevel == -1);
+ 	rgs->localGUCNestLevel = NewGUCNestLevel();
+ 
+ 	for (gstCur = 0; gstCur < GST_MAX; gstCur += 1)
+ 	{
+ 		const remoteGucInfo *rgi = &rgs->rgis[gstCur];
+ 		int gucApplyStatus;
+ 
+ 		if (!rgi->isInitialized)
+ 			continue;
+ 
+ 		gucApplyStatus = set_config_option(rgi->gucName, rgi->remoteVal,
+ 										   PGC_USERSET, PGC_S_SESSION,
+ 										   GUC_ACTION_SAVE, true, 0);
+ 		if (gucApplyStatus != 1)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("cannot load remote configuration %s "
+ 							"for type parsing",
+ 							rgi->gucName)));
+ 	}
+ }
+ 
+ /*
+  * Restore local GUCs after they have been overlaid with remote
+  * settings for type parsing, destroying the GUC nesting level.
+  */
+ static void
+ restoreLocalGucs(remoteGucs *rgs)
+ {
+ 	/*
+ 	 * A new GUCNestLevel was not introduced, so don't bother
+ 	 * restoring, either.
+ 	 */
+ 	if (rgs->localGUCNestLevel == -1)
+ 	{
+ 		return;
+ 	}
+ 
+ 	AtEOXact_GUC(false, rgs->localGUCNestLevel);
+ }
*** a/contrib/dblink/expected/dblink.out
--- b/contrib/dblink/expected/dblink.out
***************
*** 913,915 **** SELECT dblink_build_sql_delete('test_dropped', '1', 1,
--- 913,1077 ----
   DELETE FROM test_dropped WHERE id = '2'
  (1 row)
  
+ -- test the local mimicry of remote GUC values in parsing for affected
+ -- types
+ SET datestyle = ISO, MDY;
+ SET intervalstyle = postgres;
+ SET timezone = UTC;
+ SELECT dblink_connect('myconn','dbname=contrib_regression');
+  dblink_connect 
+ ----------------
+  OK
+ (1 row)
+ 
+ SELECT dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;');
+  dblink_exec 
+ -------------
+  SET
+ (1 row)
+ 
+ -- The following attempt test various paths at which TupleDescs are
+ -- formed and inspected for containment of types requiring local GUC
+ -- setting.
+ -- single row synchronous case
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00'')) t')
+ 	 AS t(a timestamptz);
+            a            
+ ------------------------
+  2013-03-12 00:00:00+00
+ (1 row)
+ 
+ -- multi-row synchronous case
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00''),
+ 		   (''12.03.2013 00:00:00+00'')) t')
+ 	 AS t(a timestamptz);
+            a            
+ ------------------------
+  2013-03-12 00:00:00+00
+  2013-03-12 00:00:00+00
+ (2 rows)
+ 
+ -- single-row asynchronous case
+ SELECT *
+ FROM dblink_send_query('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00'')) t');
+  dblink_send_query 
+ -------------------
+                  1
+ (1 row)
+ 
+ CREATE TEMPORARY TABLE result AS
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz));
+ SELECT * FROM result;
+            t            
+ ------------------------
+  2013-03-12 00:00:00+00
+ (1 row)
+ 
+ DROP TABLE result;
+ -- multi-row asynchronous case
+ SELECT *
+ FROM dblink_send_query('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00''),
+ 		   (''12.03.2013 00:00:00+00'')) t');
+  dblink_send_query 
+ -------------------
+                  1
+ (1 row)
+ 
+ CREATE TEMPORARY TABLE result AS
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz));
+ SELECT * FROM result;
+            t            
+ ------------------------
+  2013-03-12 00:00:00+00
+  2013-03-12 00:00:00+00
+ (2 rows)
+ 
+ DROP TABLE result;
+ -- Try an ambiguous interval
+ SELECT dblink_exec('myconn', 'SET intervalstyle = sql_standard;');
+  dblink_exec 
+ -------------
+  SET
+ (1 row)
+ 
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''-1 2:03:04'')) i')
+ 	 AS i(i interval);
+          i         
+ -------------------
+  -1 days -02:03:04
+ (1 row)
+ 
+ -- Try swapping to another format to ensure the GUCs are tracked
+ -- properly through a change.
+ SELECT dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;');
+  dblink_exec 
+ -------------
+  SET
+ (1 row)
+ 
+ CREATE TEMPORARY TABLE result (t timestamptz);
+ INSERT INTO result (SELECT *
+ 	   FROM dblink('myconn',
+ 			'SELECT * FROM
+ 		(VALUES (''12.03.2013 00:00:00+00'')) t')
+ 		AS t(a timestamptz));
+ SELECT dblink_exec('myconn', 'SET datestyle = ISO, MDY;');
+  dblink_exec 
+ -------------
+  SET
+ (1 row)
+ 
+ INSERT INTO result (SELECT *
+ 	   FROM dblink('myconn',
+ 			'SELECT * FROM
+ 		(VALUES (''03.12.2013 00:00:00+00'')) t')
+ 		AS t(a timestamptz));
+ SELECT DISTINCT * FROM result;
+            t            
+ ------------------------
+  2013-03-12 00:00:00+00
+ (1 row)
+ 
+ DROP TABLE result;
+ SELECT dblink_disconnect('myconn');
+  dblink_disconnect 
+ -------------------
+  OK
+ (1 row)
+ 
+ -- Make sure that the local values have retained their value in spite
+ -- of shenanigans on the connection.
+ SHOW datestyle;
+  DateStyle 
+ -----------
+  ISO, MDY
+ (1 row)
+ 
+ SHOW intervalstyle;
+  IntervalStyle 
+ ---------------
+  postgres
+ (1 row)
+ 
+ RESET datestyle;
+ RESET intervalstyle;
+ RESET timezone;
*** a/contrib/dblink/sql/dblink.sql
--- b/contrib/dblink/sql/dblink.sql
***************
*** 426,428 **** SELECT dblink_build_sql_update('test_dropped', '1', 1,
--- 426,519 ----
  
  SELECT dblink_build_sql_delete('test_dropped', '1', 1,
                                 ARRAY['2'::TEXT]);
+ 
+ -- test the local mimicry of remote GUC values in parsing for affected
+ -- types
+ SET datestyle = ISO, MDY;
+ SET intervalstyle = postgres;
+ SET timezone = UTC;
+ SELECT dblink_connect('myconn','dbname=contrib_regression');
+ SELECT dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;');
+ 
+ -- The following attempt test various paths at which TupleDescs are
+ -- formed and inspected for containment of types requiring local GUC
+ -- setting.
+ 
+ -- single row synchronous case
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00'')) t')
+ 	 AS t(a timestamptz);
+ 
+ -- multi-row synchronous case
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00''),
+ 		   (''12.03.2013 00:00:00+00'')) t')
+ 	 AS t(a timestamptz);
+ 
+ -- single-row asynchronous case
+ SELECT *
+ FROM dblink_send_query('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00'')) t');
+ CREATE TEMPORARY TABLE result AS
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz));
+ SELECT * FROM result;
+ DROP TABLE result;
+ 
+ -- multi-row asynchronous case
+ SELECT *
+ FROM dblink_send_query('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00''),
+ 		   (''12.03.2013 00:00:00+00'')) t');
+ CREATE TEMPORARY TABLE result AS
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz));
+ SELECT * FROM result;
+ DROP TABLE result;
+ 
+ -- Try an ambiguous interval
+ SELECT dblink_exec('myconn', 'SET intervalstyle = sql_standard;');
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''-1 2:03:04'')) i')
+ 	 AS i(i interval);
+ 
+ -- Try swapping to another format to ensure the GUCs are tracked
+ -- properly through a change.
+ SELECT dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;');
+ CREATE TEMPORARY TABLE result (t timestamptz);
+ INSERT INTO result (SELECT *
+ 	   FROM dblink('myconn',
+ 			'SELECT * FROM
+ 		(VALUES (''12.03.2013 00:00:00+00'')) t')
+ 		AS t(a timestamptz));
+ SELECT dblink_exec('myconn', 'SET datestyle = ISO, MDY;');
+ INSERT INTO result (SELECT *
+ 	   FROM dblink('myconn',
+ 			'SELECT * FROM
+ 		(VALUES (''03.12.2013 00:00:00+00'')) t')
+ 		AS t(a timestamptz));
+ SELECT DISTINCT * FROM result;
+ DROP TABLE result;
+ 
+ SELECT dblink_disconnect('myconn');
+ 
+ -- Make sure that the local values have retained their value in spite
+ -- of shenanigans on the connection.
+ SHOW datestyle;
+ SHOW intervalstyle;
+ 
+ RESET datestyle;
+ RESET intervalstyle;
+ RESET timezone;
#90Tom Lane
tgl@sss.pgh.pa.us
In reply to: Daniel Farina (#89)
Re: postgres_fdw vs data formatting GUCs (was Re: [v9.3] writable foreign tables)

Daniel Farina <daniel@heroku.com> writes:

Similar in purpose to cc3f281ffb0a5d9b187e7a7b7de4a045809ff683, but
taking into account that a dblink caller may choose to cause arbitrary
changes to DateStyle and IntervalStyle. To handle this, it is
necessary to use PQparameterStatus before parsing any input, every
time. This is avoided in cases that do not include the two
problematic types treated -- interval and timestamptz -- by scanning
the TupleDesc's types first.

Hm. Is that really a win? Examining the tupdesc wouldn't be free
either, and what's more, I think you're making false assumptions about
which types have behavior dependent on the GUCs. You definitely missed
out on date and plain timestamp, and what of domains over these types?

I'd be inclined to eat the cost of calling PQparameterStatus every time
(which won't be that much) and instead try to optimize by avoiding the
GUC-setting overhead if the current value matches the local setting.
But even that might be premature optimization. Did you do any
performance testing to see if there was a problem worth avoiding?

Although it has been suggested that extra_float_digits would need
similar treatment as IntervalStyle and DateStyle (as it's seen in
pg_dump), extra_float_digits is not an GUC_REPORT-ed GUC and is not
able to be checked in PQparameterStatus, and furthermore, the float4
and float8 parsers are not sensitive to the GUC anyway: both accept as
much precision as is provided in an unambiguous way.

Agreed, we don't need to worry so much about that one; or at least,
if we do need to worry, it's on the other end of the connection from
what we're fixing here.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#91Daniel Farina
daniel@heroku.com
In reply to: Tom Lane (#90)
Re: postgres_fdw vs data formatting GUCs (was Re: [v9.3] writable foreign tables)

On Tue, Mar 19, 2013 at 1:16 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Daniel Farina <daniel@heroku.com> writes:

Similar in purpose to cc3f281ffb0a5d9b187e7a7b7de4a045809ff683, but
taking into account that a dblink caller may choose to cause arbitrary
changes to DateStyle and IntervalStyle. To handle this, it is
necessary to use PQparameterStatus before parsing any input, every
time. This is avoided in cases that do not include the two
problematic types treated -- interval and timestamptz -- by scanning
the TupleDesc's types first.

Hm. Is that really a win? Examining the tupdesc wouldn't be free
either, and what's more, I think you're making false assumptions about
which types have behavior dependent on the GUCs. You definitely missed
out on date and plain timestamp, and what of domains over these types?

Yes, I also forgot about arrays, and nested composites in arrays or
nested composites. As soon as recursive types are introduced the scan
is not sufficient for sure: it's necessary to copy every GUC unless
one wants to recurse through the catalogs which feels like a heavy
loss.

I presumed at the time that skimming over the tupdecs to compare a few
Oids would be significantly cheaper than the guts of
PQparameterStatus, which I believe to be a linked list traversal. I
mostly wrote that optimization because it was easy coincidentally from
how I chose to structure the code rather than belief in its absolute
necessity.

And, as you said, I forgot a few types even for the simple case, which
is a bit of a relief because some of the machinery I wrote for the n =
3 case will come in useful for that.

I'd be inclined to eat the cost of calling PQparameterStatus every time
(which won't be that much) and instead try to optimize by avoiding the
GUC-setting overhead if the current value matches the local setting.
But even that might be premature optimization. Did you do any
performance testing to see if there was a problem worth avoiding?

Nope; should I invent a new way to do that, or would it be up to
commit standard based on inspection alone? I'm okay either way, let
me know.

I'll take into account these problems and post a new version.

--
fdr

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#92Tom Lane
tgl@sss.pgh.pa.us
In reply to: Daniel Farina (#91)
Re: postgres_fdw vs data formatting GUCs (was Re: [v9.3] writable foreign tables)

Daniel Farina <daniel@heroku.com> writes:

On Tue, Mar 19, 2013 at 1:16 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

I'd be inclined to eat the cost of calling PQparameterStatus every time
(which won't be that much) and instead try to optimize by avoiding the
GUC-setting overhead if the current value matches the local setting.
But even that might be premature optimization. Did you do any
performance testing to see if there was a problem worth avoiding?

Nope; should I invent a new way to do that, or would it be up to
commit standard based on inspection alone? I'm okay either way, let
me know.

Doesn't seem that hard to test: run a dblink query that pulls back a
bunch of data under best-case conditions (ie, local not remote server),
and time it with and without the proposed fix. If the difference
is marginal then it's not worth working hard to optimize.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#93Daniel Farina
daniel@heroku.com
In reply to: Tom Lane (#92)
1 attachment(s)
Re: postgres_fdw vs data formatting GUCs (was Re: [v9.3] writable foreign tables)

On Tue, Mar 19, 2013 at 2:41 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Daniel Farina <daniel@heroku.com> writes:

On Tue, Mar 19, 2013 at 1:16 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

I'd be inclined to eat the cost of calling PQparameterStatus every time
(which won't be that much) and instead try to optimize by avoiding the
GUC-setting overhead if the current value matches the local setting.
But even that might be premature optimization. Did you do any
performance testing to see if there was a problem worth avoiding?

Nope; should I invent a new way to do that, or would it be up to
commit standard based on inspection alone? I'm okay either way, let
me know.

Doesn't seem that hard to test: run a dblink query that pulls back a
bunch of data under best-case conditions (ie, local not remote server),
and time it with and without the proposed fix. If the difference
is marginal then it's not worth working hard to optimize.

Okay, will do, and here's the shorter and less mechanically intensive
naive version that I think is the baseline: it doesn't try to optimize
out any GUC settings and sets up the GUCs before the two
materialization paths in dblink.

Something I forgot to ask about is about if an strangely-minded type
input function could whack around the GUC as records are being
remitted, which would necessitate per-tuple polling of
pqParameterStatus (e.g. in the inner loop of a materialization) .
That seemed pretty "out there," but I'm broaching it for completeness.

I'll see how much of a penalty it is vs. not applying any patch at all next.

--
fdr

Attachments:

dblink-guc-sensitive-types-v2.patchapplication/octet-stream; name=dblink-guc-sensitive-types-v2.patchDownload
dblink: perform local-GUC modification to parse GUC-sensitive types

Similar in purpose to cc3f281ffb0a5d9b187e7a7b7de4a045809ff683, but
taking into account that a dblink caller may choose to cause arbitrary
changes to DateStyle and IntervalStyle.  To handle this, it is
necessary to use PQparameterStatus before parsing any input, every
time.

Signed-off-by: Daniel Farina <daniel@fdr.io>
---
 contrib/dblink/dblink.c            |  139 ++++++++++++++++++++++++++++++-
 contrib/dblink/expected/dblink.out |  162 ++++++++++++++++++++++++++++++++++++
 contrib/dblink/sql/dblink.sql      |   91 ++++++++++++++++++++
 3 files changed, 389 insertions(+), 3 deletions(-)

*** a/contrib/dblink/dblink.c
--- b/contrib/dblink/dblink.c
***************
*** 53,58 ****
--- 53,59 ----
  #include "utils/acl.h"
  #include "utils/builtins.h"
  #include "utils/fmgroids.h"
+ #include "utils/guc.h"
  #include "utils/lsyscache.h"
  #include "utils/memutils.h"
  #include "utils/rel.h"
***************
*** 69,74 **** typedef struct remoteConn
--- 70,94 ----
  	bool		newXactForCursor;		/* Opened a transaction for a cursor */
  } remoteConn;
  
+ 
+ const char *parseAffectingGucs[] = {"DateStyle", "IntervalStyle"};
+ 
+ /*
+  * Contains information to save and restore GUCs for types with
+  * GUC-sensitive parsing.
+  */
+ typedef struct remoteGucs
+ {
+ 	/*
+ 	 * GUC nesting level.  Set to -1 if no GUC nesting level has been
+ 	 * introduced.
+ 	 */
+ 	int localGUCNestLevel;
+ 
+ 	/* Kept around for PQparameterStatus to interrogate remote GUCs */
+ 	PGconn *conn;
+ } remoteGucs;
+ 
  typedef struct storeInfo
  {
  	FunctionCallInfo fcinfo;
***************
*** 118,123 **** static void validate_pkattnums(Relation rel,
--- 138,146 ----
  				   int **pkattnums, int *pknumatts);
  static bool is_valid_dblink_option(const PQconninfoOption *options,
  					   const char *option, Oid context);
+ static void initRemoteGucs(remoteGucs *rgs, PGconn *conn);
+ static void applyRemoteGucs(remoteGucs *rgs);
+ static void restoreLocalGucs(remoteGucs *rgs);
  
  /* Global */
  static remoteConn *pconn = NULL;
***************
*** 531,536 **** dblink_fetch(PG_FUNCTION_ARGS)
--- 554,560 ----
  	char	   *curname = NULL;
  	int			howmany = 0;
  	bool		fail = true;	/* default to backward compatible */
+ 	remoteGucs	rgs;
  
  	prepTuplestoreResult(fcinfo);
  
***************
*** 605,611 **** dblink_fetch(PG_FUNCTION_ARGS)
  				 errmsg("cursor \"%s\" does not exist", curname)));
  	}
  
! 	materializeResult(fcinfo, res);
  	return (Datum) 0;
  }
  
--- 629,653 ----
  				 errmsg("cursor \"%s\" does not exist", curname)));
  	}
  
! 	/*
! 	 * Materialize the result, before doing so set GUCs that may
! 	 * affect parsing and then un-set them afterwards.
! 	 */
! 	initRemoteGucs(&rgs, conn);
! 
! 	PG_TRY();
! 	{
! 		applyRemoteGucs(&rgs);
! 		materializeResult(fcinfo, res);
! 	}
! 	PG_CATCH();
! 	{
! 		restoreLocalGucs(&rgs);
! 	}
! 	PG_END_TRY();
! 
! 	restoreLocalGucs(&rgs);
! 
  	return (Datum) 0;
  }
  
***************
*** 656,665 **** dblink_get_result(PG_FUNCTION_ARGS)
  static Datum
  dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
  {
! 	PGconn	   *volatile conn = NULL;
! 	volatile bool freeconn = false;
  
  	prepTuplestoreResult(fcinfo);
  
  	DBLINK_INIT;
  
--- 698,709 ----
  static Datum
  dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
  {
! 	PGconn	   *volatile		conn	 = NULL;
! 	volatile bool				freeconn = false;
! 	remoteGucs					rgs;
  
  	prepTuplestoreResult(fcinfo);
+ 	initRemoteGucs(&rgs, NULL);
  
  	DBLINK_INIT;
  
***************
*** 728,735 **** dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
--- 772,787 ----
  		if (!conn)
  			DBLINK_CONN_NOT_AVAIL;
  
+ 		initRemoteGucs(&rgs, conn);
+ 
  		if (!is_async)
  		{
+ 			/*
+ 			 * Before parsing input, synchronize local
+ 			 * type-parsing-affecting GUCs with the remote GUC value.
+ 			 */
+ 			applyRemoteGucs(&rgs);
+ 
  			/* synchronous query, use efficient tuple collection method */
  			materializeQueryResult(fcinfo, conn, conname, sql, fail);
  		}
***************
*** 750,755 **** dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
--- 802,814 ----
  				}
  				else
  				{
+ 					/*
+ 					 * Before parsing input, synchronize local
+ 					 * type-parsing-affecting GUCs with the remote GUC
+ 					 * value.
+ 					 */
+ 					applyRemoteGucs(&rgs);
+ 
  					materializeResult(fcinfo, res);
  				}
  			}
***************
*** 760,765 **** dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
--- 819,828 ----
  		/* if needed, close the connection to the database */
  		if (freeconn)
  			PQfinish(conn);
+ 
+ 		/* Pop any set GUCs, if necessary */
+ 		restoreLocalGucs(&rgs);
+ 
  		PG_RE_THROW();
  	}
  	PG_END_TRY();
***************
*** 768,773 **** dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
--- 831,839 ----
  	if (freeconn)
  		PQfinish(conn);
  
+ 	/* Pop any set GUCs, if necessary */
+ 	restoreLocalGucs(&rgs);
+ 
  	return (Datum) 0;
  }
  
***************
*** 2898,2900 **** is_valid_dblink_option(const PQconninfoOption *options, const char *option,
--- 2964,3033 ----
  
  	return true;
  }
+ 
+ /* Initializer for a "remoteGucs" struct value. */
+ static void
+ initRemoteGucs(remoteGucs *rgs, PGconn *conn)
+ {
+ 	rgs->localGUCNestLevel = -1;
+ 	rgs->conn			   = conn;
+ }
+ 
+ /*
+  * Scan a TupleDesc and, should it contain types that are sensitive to
+  * GUCs, acquire remote GUCs and set them in a new GUC nesting level.
+  * This is undone with restoreLocalGucs.
+  */
+ static void
+ applyRemoteGucs(remoteGucs *rgs)
+ {
+ 	int i;
+ 	const int numGucs = sizeof parseAffectingGucs / sizeof *parseAffectingGucs;
+ 
+ 	/*
+ 	 * Affected types require local GUC manipulations.  Create a new
+ 	 * GUC NestLevel to overlay the remote settings.
+ 	 *
+ 	 * Also, this nesting is done exactly once per remoteGucInfo
+ 	 * structure, so expect it to come with an invalid NestLevel.
+ 	 */
+ 	Assert(rgs->localGUCNestLevel == -1);
+ 	rgs->localGUCNestLevel = NewGUCNestLevel();
+ 
+ 	for (i = 0; i < numGucs; i += 1)
+ 	{
+ 		const char		*gucName   = parseAffectingGucs[i];
+ 		const char		*remoteVal = PQparameterStatus(rgs->conn, gucName);
+ 
+ 		int gucApplyStatus;
+ 
+ 		gucApplyStatus = set_config_option(gucName, remoteVal,
+ 										   PGC_USERSET, PGC_S_SESSION,
+ 										   GUC_ACTION_SAVE, true, 0);
+ 		if (gucApplyStatus != 1)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("cannot load remote configuration %s "
+ 							"for type parsing",
+ 							gucName)));
+ 	}
+ }
+ 
+ /*
+  * Restore local GUCs after they have been overlaid with remote
+  * settings for type parsing, destroying the GUC nesting level.
+  */
+ static void
+ restoreLocalGucs(remoteGucs *rgs)
+ {
+ 	/*
+ 	 * A new GUCNestLevel was not introduced, so don't bother
+ 	 * restoring, either.
+ 	 */
+ 	if (rgs->localGUCNestLevel == -1)
+ 	{
+ 		return;
+ 	}
+ 
+ 	AtEOXact_GUC(false, rgs->localGUCNestLevel);
+ }
*** a/contrib/dblink/expected/dblink.out
--- b/contrib/dblink/expected/dblink.out
***************
*** 913,915 **** SELECT dblink_build_sql_delete('test_dropped', '1', 1,
--- 913,1077 ----
   DELETE FROM test_dropped WHERE id = '2'
  (1 row)
  
+ -- test the local mimicry of remote GUC values in parsing for affected
+ -- types
+ SET datestyle = ISO, MDY;
+ SET intervalstyle = postgres;
+ SET timezone = UTC;
+ SELECT dblink_connect('myconn','dbname=contrib_regression');
+  dblink_connect 
+ ----------------
+  OK
+ (1 row)
+ 
+ SELECT dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;');
+  dblink_exec 
+ -------------
+  SET
+ (1 row)
+ 
+ -- The following attempt test various paths at which TupleDescs are
+ -- formed and inspected for containment of types requiring local GUC
+ -- setting.
+ -- single row synchronous case
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00'')) t')
+ 	 AS t(a timestamptz);
+            a            
+ ------------------------
+  2013-03-12 00:00:00+00
+ (1 row)
+ 
+ -- multi-row synchronous case
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00''),
+ 		   (''12.03.2013 00:00:00+00'')) t')
+ 	 AS t(a timestamptz);
+            a            
+ ------------------------
+  2013-03-12 00:00:00+00
+  2013-03-12 00:00:00+00
+ (2 rows)
+ 
+ -- single-row asynchronous case
+ SELECT *
+ FROM dblink_send_query('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00'')) t');
+  dblink_send_query 
+ -------------------
+                  1
+ (1 row)
+ 
+ CREATE TEMPORARY TABLE result AS
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz));
+ SELECT * FROM result;
+            t            
+ ------------------------
+  2013-03-12 00:00:00+00
+ (1 row)
+ 
+ DROP TABLE result;
+ -- multi-row asynchronous case
+ SELECT *
+ FROM dblink_send_query('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00''),
+ 		   (''12.03.2013 00:00:00+00'')) t');
+  dblink_send_query 
+ -------------------
+                  1
+ (1 row)
+ 
+ CREATE TEMPORARY TABLE result AS
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz));
+ SELECT * FROM result;
+            t            
+ ------------------------
+  2013-03-12 00:00:00+00
+  2013-03-12 00:00:00+00
+ (2 rows)
+ 
+ DROP TABLE result;
+ -- Try an ambiguous interval
+ SELECT dblink_exec('myconn', 'SET intervalstyle = sql_standard;');
+  dblink_exec 
+ -------------
+  SET
+ (1 row)
+ 
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''-1 2:03:04'')) i')
+ 	 AS i(i interval);
+          i         
+ -------------------
+  -1 days -02:03:04
+ (1 row)
+ 
+ -- Try swapping to another format to ensure the GUCs are tracked
+ -- properly through a change.
+ SELECT dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;');
+  dblink_exec 
+ -------------
+  SET
+ (1 row)
+ 
+ CREATE TEMPORARY TABLE result (t timestamptz);
+ INSERT INTO result (SELECT *
+ 	   FROM dblink('myconn',
+ 			'SELECT * FROM
+ 		(VALUES (''12.03.2013 00:00:00+00'')) t')
+ 		AS t(a timestamptz));
+ SELECT dblink_exec('myconn', 'SET datestyle = ISO, MDY;');
+  dblink_exec 
+ -------------
+  SET
+ (1 row)
+ 
+ INSERT INTO result (SELECT *
+ 	   FROM dblink('myconn',
+ 			'SELECT * FROM
+ 		(VALUES (''03.12.2013 00:00:00+00'')) t')
+ 		AS t(a timestamptz));
+ SELECT DISTINCT * FROM result;
+            t            
+ ------------------------
+  2013-03-12 00:00:00+00
+ (1 row)
+ 
+ DROP TABLE result;
+ SELECT dblink_disconnect('myconn');
+  dblink_disconnect 
+ -------------------
+  OK
+ (1 row)
+ 
+ -- Make sure that the local values have retained their value in spite
+ -- of shenanigans on the connection.
+ SHOW datestyle;
+  DateStyle 
+ -----------
+  ISO, MDY
+ (1 row)
+ 
+ SHOW intervalstyle;
+  IntervalStyle 
+ ---------------
+  postgres
+ (1 row)
+ 
+ RESET datestyle;
+ RESET intervalstyle;
+ RESET timezone;
*** a/contrib/dblink/sql/dblink.sql
--- b/contrib/dblink/sql/dblink.sql
***************
*** 426,428 **** SELECT dblink_build_sql_update('test_dropped', '1', 1,
--- 426,519 ----
  
  SELECT dblink_build_sql_delete('test_dropped', '1', 1,
                                 ARRAY['2'::TEXT]);
+ 
+ -- test the local mimicry of remote GUC values in parsing for affected
+ -- types
+ SET datestyle = ISO, MDY;
+ SET intervalstyle = postgres;
+ SET timezone = UTC;
+ SELECT dblink_connect('myconn','dbname=contrib_regression');
+ SELECT dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;');
+ 
+ -- The following attempt test various paths at which TupleDescs are
+ -- formed and inspected for containment of types requiring local GUC
+ -- setting.
+ 
+ -- single row synchronous case
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00'')) t')
+ 	 AS t(a timestamptz);
+ 
+ -- multi-row synchronous case
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00''),
+ 		   (''12.03.2013 00:00:00+00'')) t')
+ 	 AS t(a timestamptz);
+ 
+ -- single-row asynchronous case
+ SELECT *
+ FROM dblink_send_query('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00'')) t');
+ CREATE TEMPORARY TABLE result AS
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz));
+ SELECT * FROM result;
+ DROP TABLE result;
+ 
+ -- multi-row asynchronous case
+ SELECT *
+ FROM dblink_send_query('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00''),
+ 		   (''12.03.2013 00:00:00+00'')) t');
+ CREATE TEMPORARY TABLE result AS
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz));
+ SELECT * FROM result;
+ DROP TABLE result;
+ 
+ -- Try an ambiguous interval
+ SELECT dblink_exec('myconn', 'SET intervalstyle = sql_standard;');
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''-1 2:03:04'')) i')
+ 	 AS i(i interval);
+ 
+ -- Try swapping to another format to ensure the GUCs are tracked
+ -- properly through a change.
+ SELECT dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;');
+ CREATE TEMPORARY TABLE result (t timestamptz);
+ INSERT INTO result (SELECT *
+ 	   FROM dblink('myconn',
+ 			'SELECT * FROM
+ 		(VALUES (''12.03.2013 00:00:00+00'')) t')
+ 		AS t(a timestamptz));
+ SELECT dblink_exec('myconn', 'SET datestyle = ISO, MDY;');
+ INSERT INTO result (SELECT *
+ 	   FROM dblink('myconn',
+ 			'SELECT * FROM
+ 		(VALUES (''03.12.2013 00:00:00+00'')) t')
+ 		AS t(a timestamptz));
+ SELECT DISTINCT * FROM result;
+ DROP TABLE result;
+ 
+ SELECT dblink_disconnect('myconn');
+ 
+ -- Make sure that the local values have retained their value in spite
+ -- of shenanigans on the connection.
+ SHOW datestyle;
+ SHOW intervalstyle;
+ 
+ RESET datestyle;
+ RESET intervalstyle;
+ RESET timezone;
#94Daniel Farina
daniel@heroku.com
In reply to: Daniel Farina (#93)
1 attachment(s)
Re: postgres_fdw vs data formatting GUCs (was Re: [v9.3] writable foreign tables)

On Tue, Mar 19, 2013 at 3:06 PM, Daniel Farina <daniel@heroku.com> wrote:

On Tue, Mar 19, 2013 at 2:41 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Daniel Farina <daniel@heroku.com> writes:

On Tue, Mar 19, 2013 at 1:16 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

I'd be inclined to eat the cost of calling PQparameterStatus every time
(which won't be that much) and instead try to optimize by avoiding the
GUC-setting overhead if the current value matches the local setting.
But even that might be premature optimization. Did you do any
performance testing to see if there was a problem worth avoiding?

Nope; should I invent a new way to do that, or would it be up to
commit standard based on inspection alone? I'm okay either way, let
me know.

Doesn't seem that hard to test: run a dblink query that pulls back a
bunch of data under best-case conditions (ie, local not remote server),
and time it with and without the proposed fix. If the difference
is marginal then it's not worth working hard to optimize.

Okay, will do, and here's the shorter and less mechanically intensive
naive version that I think is the baseline: it doesn't try to optimize
out any GUC settings and sets up the GUCs before the two
materialization paths in dblink.

The results. Summary: seems like grabbing the GUC and strcmp-ing is
worthwhile, but the amount of ping-ponging between processes adds some
noise to the timing results: utilization is far short of 100% on
either processor involved. Attached is a cumulative diff of the new
version, and also reproduced below are the changes to v2 that make up
v3.

## Benchmark

SELECT dblink_connect('benchconn','dbname=contrib_regression');

CREATE FUNCTION bench() RETURNS integer AS $$
DECLARE
iterations integer;
BEGIN
iterations := 0;

WHILE iterations < 300000 LOOP
PERFORM * FROM dblink('benchconn', 'SELECT 1') AS t(a int);
iterations := iterations + 1;
END LOOP;

RETURN iterations;
END;
$$ LANGUAGE plpgsql;

SELECT clock_timestamp() INTO TEMP beginning;
SELECT bench();
SELECT clock_timestamp() INTO TEMP ending;

SELECT 'dblink-benchmark-lines';
SELECT ending.clock_timestamp - beginning.clock_timestamp
FROM beginning, ending;

## Data Setup

CREATE TEMP TABLE bench_results (version text, span interval);

COPY bench_results FROM STDIN WITH CSV;
no-patch,@ 41.308122 secs
no-patch,@ 36.63597 secs
no-patch,@ 34.264119 secs
no-patch,@ 34.760179 secs
no-patch,@ 32.991257 secs
no-patch,@ 34.538258 secs
no-patch,@ 42.576354 secs
no-patch,@ 39.335557 secs
no-patch,@ 37.493206 secs
no-patch,@ 37.812205 secs
v2-applied,@ 36.550553 secs
v2-applied,@ 38.608723 secs
v2-applied,@ 39.415744 secs
v2-applied,@ 46.091052 secs
v2-applied,@ 45.425438 secs
v2-applied,@ 48.219506 secs
v2-applied,@ 43.514878 secs
v2-applied,@ 45.892302 secs
v2-applied,@ 48.479335 secs
v2-applied,@ 47.632041 secs
v3-strcmp,@ 32.524385 secs
v3-strcmp,@ 34.982098 secs
v3-strcmp,@ 34.487222 secs
v3-strcmp,@ 44.394681 secs
v3-strcmp,@ 44.638309 secs
v3-strcmp,@ 44.113088 secs
v3-strcmp,@ 45.497769 secs
v3-strcmp,@ 33.530176 secs
v3-strcmp,@ 32.9704 secs
v3-strcmp,@ 40.84764 secs
\.

=> SELECT version, avg(extract(epoch from span)), stddev(extract(epoch
from span))
FROM bench_results
GROUP BY version;
version | avg | stddev
------------+------------+------------------
no-patch | 37.1715227 | 3.17076487912923
v2-applied | 43.9829572 | 4.30572672565711
v3-strcmp | 38.7985768 | 5.54760393720725

## Changes to v2:

--- a/contrib/dblink/dblink.c
+++ b/contrib/dblink/dblink.c
@@ -2981,9 +2981,11 @@ initRemoteGucs(remoteGucs *rgs, PGconn *conn)
 static void
 applyRemoteGucs(remoteGucs *rgs)
 {
- int i;
  const int numGucs = sizeof parseAffectingGucs / sizeof *parseAffectingGucs;
+ int i;
+ int addedGucNesting = false;
+
  /*
  * Affected types require local GUC manipulations.  Create a new
  * GUC NestLevel to overlay the remote settings.
@@ -2992,14 +2994,27 @@ applyRemoteGucs(remoteGucs *rgs)
  * structure, so expect it to come with an invalid NestLevel.
  */
  Assert(rgs->localGUCNestLevel == -1);
- rgs->localGUCNestLevel = NewGUCNestLevel();
  for (i = 0; i < numGucs; i += 1)
  {
+ int gucApplyStatus;
+
  const char *gucName   = parseAffectingGucs[i];
  const char *remoteVal = PQparameterStatus(rgs->conn, gucName);
+ const char *localVal  = GetConfigOption(gucName, true, true);
- int gucApplyStatus;
+ /*
+ * Attempt to avoid GUC setting if the remote and local GUCs
+ * already have the same value.
+ */
+ if (strcmp(remoteVal, localVal) == 0)
+ continue;
+
+ if (!addedGucNesting)
+ {
+ rgs->localGUCNestLevel = NewGUCNestLevel();
+ addedGucNesting = true;
+ }

gucApplyStatus = set_config_option(gucName, remoteVal,
PGC_USERSET, PGC_S_SESSION,

--
fdr

Attachments:

dblink-guc-sensitive-types-v3.patchapplication/octet-stream; name=dblink-guc-sensitive-types-v3.patchDownload
dblink: perform local-GUC modification to parse GUC-sensitive types

Similar in purpose to cc3f281ffb0a5d9b187e7a7b7de4a045809ff683, but
taking into account that a dblink caller may choose to cause arbitrary
changes to DateStyle and IntervalStyle.  To handle this, it is
necessary to use PQparameterStatus before parsing any input, every
time.

---
 contrib/dblink/dblink.c            |  154 +++++++++++++++++++++++++-
 contrib/dblink/expected/dblink.out |  162 ++++++++++++++++++++++++++++
 contrib/dblink/sql/dblink.sql      |   91 ++++++++++++++++
 3 files changed, 404 insertions(+), 3 deletions(-)
*** a/contrib/dblink/dblink.c
--- b/contrib/dblink/dblink.c
***************
*** 53,58 ****
--- 53,59 ----
  #include "utils/acl.h"
  #include "utils/builtins.h"
  #include "utils/fmgroids.h"
+ #include "utils/guc.h"
  #include "utils/lsyscache.h"
  #include "utils/memutils.h"
  #include "utils/rel.h"
***************
*** 69,74 **** typedef struct remoteConn
--- 70,94 ----
  	bool		newXactForCursor;		/* Opened a transaction for a cursor */
  } remoteConn;
  
+ 
+ const char *parseAffectingGucs[] = {"DateStyle", "IntervalStyle"};
+ 
+ /*
+  * Contains information to save and restore GUCs for types with
+  * GUC-sensitive parsing.
+  */
+ typedef struct remoteGucs
+ {
+ 	/*
+ 	 * GUC nesting level.  Set to -1 if no GUC nesting level has been
+ 	 * introduced.
+ 	 */
+ 	int localGUCNestLevel;
+ 
+ 	/* Kept around for PQparameterStatus to interrogate remote GUCs */
+ 	PGconn *conn;
+ } remoteGucs;
+ 
  typedef struct storeInfo
  {
  	FunctionCallInfo fcinfo;
***************
*** 118,123 **** static void validate_pkattnums(Relation rel,
--- 138,146 ----
  				   int **pkattnums, int *pknumatts);
  static bool is_valid_dblink_option(const PQconninfoOption *options,
  					   const char *option, Oid context);
+ static void initRemoteGucs(remoteGucs *rgs, PGconn *conn);
+ static void applyRemoteGucs(remoteGucs *rgs);
+ static void restoreLocalGucs(remoteGucs *rgs);
  
  /* Global */
  static remoteConn *pconn = NULL;
***************
*** 531,536 **** dblink_fetch(PG_FUNCTION_ARGS)
--- 554,560 ----
  	char	   *curname = NULL;
  	int			howmany = 0;
  	bool		fail = true;	/* default to backward compatible */
+ 	remoteGucs	rgs;
  
  	prepTuplestoreResult(fcinfo);
  
***************
*** 605,611 **** dblink_fetch(PG_FUNCTION_ARGS)
  				 errmsg("cursor \"%s\" does not exist", curname)));
  	}
  
! 	materializeResult(fcinfo, res);
  	return (Datum) 0;
  }
  
--- 629,653 ----
  				 errmsg("cursor \"%s\" does not exist", curname)));
  	}
  
! 	/*
! 	 * Materialize the result, before doing so set GUCs that may
! 	 * affect parsing and then un-set them afterwards.
! 	 */
! 	initRemoteGucs(&rgs, conn);
! 
! 	PG_TRY();
! 	{
! 		applyRemoteGucs(&rgs);
! 		materializeResult(fcinfo, res);
! 	}
! 	PG_CATCH();
! 	{
! 		restoreLocalGucs(&rgs);
! 	}
! 	PG_END_TRY();
! 
! 	restoreLocalGucs(&rgs);
! 
  	return (Datum) 0;
  }
  
***************
*** 656,665 **** dblink_get_result(PG_FUNCTION_ARGS)
  static Datum
  dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
  {
! 	PGconn	   *volatile conn = NULL;
! 	volatile bool freeconn = false;
  
  	prepTuplestoreResult(fcinfo);
  
  	DBLINK_INIT;
  
--- 698,709 ----
  static Datum
  dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
  {
! 	PGconn	   *volatile		conn	 = NULL;
! 	volatile bool				freeconn = false;
! 	remoteGucs					rgs;
  
  	prepTuplestoreResult(fcinfo);
+ 	initRemoteGucs(&rgs, NULL);
  
  	DBLINK_INIT;
  
***************
*** 728,735 **** dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
--- 772,787 ----
  		if (!conn)
  			DBLINK_CONN_NOT_AVAIL;
  
+ 		initRemoteGucs(&rgs, conn);
+ 
  		if (!is_async)
  		{
+ 			/*
+ 			 * Before parsing input, synchronize local
+ 			 * type-parsing-affecting GUCs with the remote GUC value.
+ 			 */
+ 			applyRemoteGucs(&rgs);
+ 
  			/* synchronous query, use efficient tuple collection method */
  			materializeQueryResult(fcinfo, conn, conname, sql, fail);
  		}
***************
*** 750,755 **** dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
--- 802,814 ----
  				}
  				else
  				{
+ 					/*
+ 					 * Before parsing input, synchronize local
+ 					 * type-parsing-affecting GUCs with the remote GUC
+ 					 * value.
+ 					 */
+ 					applyRemoteGucs(&rgs);
+ 
  					materializeResult(fcinfo, res);
  				}
  			}
***************
*** 760,765 **** dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
--- 819,828 ----
  		/* if needed, close the connection to the database */
  		if (freeconn)
  			PQfinish(conn);
+ 
+ 		/* Pop any set GUCs, if necessary */
+ 		restoreLocalGucs(&rgs);
+ 
  		PG_RE_THROW();
  	}
  	PG_END_TRY();
***************
*** 768,773 **** dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
--- 831,839 ----
  	if (freeconn)
  		PQfinish(conn);
  
+ 	/* Pop any set GUCs, if necessary */
+ 	restoreLocalGucs(&rgs);
+ 
  	return (Datum) 0;
  }
  
***************
*** 2898,2900 **** is_valid_dblink_option(const PQconninfoOption *options, const char *option,
--- 2964,3048 ----
  
  	return true;
  }
+ 
+ /* Initializer for a "remoteGucs" struct value. */
+ static void
+ initRemoteGucs(remoteGucs *rgs, PGconn *conn)
+ {
+ 	rgs->localGUCNestLevel = -1;
+ 	rgs->conn			   = conn;
+ }
+ 
+ /*
+  * Scan a TupleDesc and, should it contain types that are sensitive to
+  * GUCs, acquire remote GUCs and set them in a new GUC nesting level.
+  * This is undone with restoreLocalGucs.
+  */
+ static void
+ applyRemoteGucs(remoteGucs *rgs)
+ {
+ 	const int numGucs = sizeof parseAffectingGucs / sizeof *parseAffectingGucs;
+ 
+ 	int i;
+ 	int addedGucNesting = false;
+ 
+ 	/*
+ 	 * Affected types require local GUC manipulations.  Create a new
+ 	 * GUC NestLevel to overlay the remote settings.
+ 	 *
+ 	 * Also, this nesting is done exactly once per remoteGucInfo
+ 	 * structure, so expect it to come with an invalid NestLevel.
+ 	 */
+ 	Assert(rgs->localGUCNestLevel == -1);
+ 
+ 	for (i = 0; i < numGucs; i += 1)
+ 	{
+ 		int gucApplyStatus;
+ 
+ 		const char		*gucName   = parseAffectingGucs[i];
+ 		const char		*remoteVal = PQparameterStatus(rgs->conn, gucName);
+ 		const char		*localVal  = GetConfigOption(gucName, true, true);
+ 
+ 		/*
+ 		 * Attempt to avoid GUC setting if the remote and local GUCs
+ 		 * already have the same value.
+ 		 */
+ 		if (strcmp(remoteVal, localVal) == 0)
+ 			continue;
+ 
+ 		if (!addedGucNesting)
+ 		{
+ 			rgs->localGUCNestLevel = NewGUCNestLevel();
+ 			addedGucNesting = true;
+ 		}
+ 
+ 		gucApplyStatus = set_config_option(gucName, remoteVal,
+ 										   PGC_USERSET, PGC_S_SESSION,
+ 										   GUC_ACTION_SAVE, true, 0);
+ 		if (gucApplyStatus != 1)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("cannot load remote configuration %s "
+ 							"for type parsing",
+ 							gucName)));
+ 	}
+ }
+ 
+ /*
+  * Restore local GUCs after they have been overlaid with remote
+  * settings for type parsing, destroying the GUC nesting level.
+  */
+ static void
+ restoreLocalGucs(remoteGucs *rgs)
+ {
+ 	/*
+ 	 * A new GUCNestLevel was not introduced, so don't bother
+ 	 * restoring, either.
+ 	 */
+ 	if (rgs->localGUCNestLevel == -1)
+ 	{
+ 		return;
+ 	}
+ 
+ 	AtEOXact_GUC(false, rgs->localGUCNestLevel);
+ }
*** a/contrib/dblink/expected/dblink.out
--- b/contrib/dblink/expected/dblink.out
***************
*** 913,915 **** SELECT dblink_build_sql_delete('test_dropped', '1', 1,
--- 913,1077 ----
   DELETE FROM test_dropped WHERE id = '2'
  (1 row)
  
+ -- test the local mimicry of remote GUC values in parsing for affected
+ -- types
+ SET datestyle = ISO, MDY;
+ SET intervalstyle = postgres;
+ SET timezone = UTC;
+ SELECT dblink_connect('myconn','dbname=contrib_regression');
+  dblink_connect 
+ ----------------
+  OK
+ (1 row)
+ 
+ SELECT dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;');
+  dblink_exec 
+ -------------
+  SET
+ (1 row)
+ 
+ -- The following attempt test various paths at which TupleDescs are
+ -- formed and inspected for containment of types requiring local GUC
+ -- setting.
+ -- single row synchronous case
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00'')) t')
+ 	 AS t(a timestamptz);
+            a            
+ ------------------------
+  2013-03-12 00:00:00+00
+ (1 row)
+ 
+ -- multi-row synchronous case
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00''),
+ 		   (''12.03.2013 00:00:00+00'')) t')
+ 	 AS t(a timestamptz);
+            a            
+ ------------------------
+  2013-03-12 00:00:00+00
+  2013-03-12 00:00:00+00
+ (2 rows)
+ 
+ -- single-row asynchronous case
+ SELECT *
+ FROM dblink_send_query('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00'')) t');
+  dblink_send_query 
+ -------------------
+                  1
+ (1 row)
+ 
+ CREATE TEMPORARY TABLE result AS
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz));
+ SELECT * FROM result;
+            t            
+ ------------------------
+  2013-03-12 00:00:00+00
+ (1 row)
+ 
+ DROP TABLE result;
+ -- multi-row asynchronous case
+ SELECT *
+ FROM dblink_send_query('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00''),
+ 		   (''12.03.2013 00:00:00+00'')) t');
+  dblink_send_query 
+ -------------------
+                  1
+ (1 row)
+ 
+ CREATE TEMPORARY TABLE result AS
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz));
+ SELECT * FROM result;
+            t            
+ ------------------------
+  2013-03-12 00:00:00+00
+  2013-03-12 00:00:00+00
+ (2 rows)
+ 
+ DROP TABLE result;
+ -- Try an ambiguous interval
+ SELECT dblink_exec('myconn', 'SET intervalstyle = sql_standard;');
+  dblink_exec 
+ -------------
+  SET
+ (1 row)
+ 
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''-1 2:03:04'')) i')
+ 	 AS i(i interval);
+          i         
+ -------------------
+  -1 days -02:03:04
+ (1 row)
+ 
+ -- Try swapping to another format to ensure the GUCs are tracked
+ -- properly through a change.
+ SELECT dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;');
+  dblink_exec 
+ -------------
+  SET
+ (1 row)
+ 
+ CREATE TEMPORARY TABLE result (t timestamptz);
+ INSERT INTO result (SELECT *
+ 	   FROM dblink('myconn',
+ 			'SELECT * FROM
+ 		(VALUES (''12.03.2013 00:00:00+00'')) t')
+ 		AS t(a timestamptz));
+ SELECT dblink_exec('myconn', 'SET datestyle = ISO, MDY;');
+  dblink_exec 
+ -------------
+  SET
+ (1 row)
+ 
+ INSERT INTO result (SELECT *
+ 	   FROM dblink('myconn',
+ 			'SELECT * FROM
+ 		(VALUES (''03.12.2013 00:00:00+00'')) t')
+ 		AS t(a timestamptz));
+ SELECT DISTINCT * FROM result;
+            t            
+ ------------------------
+  2013-03-12 00:00:00+00
+ (1 row)
+ 
+ DROP TABLE result;
+ SELECT dblink_disconnect('myconn');
+  dblink_disconnect 
+ -------------------
+  OK
+ (1 row)
+ 
+ -- Make sure that the local values have retained their value in spite
+ -- of shenanigans on the connection.
+ SHOW datestyle;
+  DateStyle 
+ -----------
+  ISO, MDY
+ (1 row)
+ 
+ SHOW intervalstyle;
+  IntervalStyle 
+ ---------------
+  postgres
+ (1 row)
+ 
+ RESET datestyle;
+ RESET intervalstyle;
+ RESET timezone;
*** a/contrib/dblink/sql/dblink.sql
--- b/contrib/dblink/sql/dblink.sql
***************
*** 426,428 **** SELECT dblink_build_sql_update('test_dropped', '1', 1,
--- 426,519 ----
  
  SELECT dblink_build_sql_delete('test_dropped', '1', 1,
                                 ARRAY['2'::TEXT]);
+ 
+ -- test the local mimicry of remote GUC values in parsing for affected
+ -- types
+ SET datestyle = ISO, MDY;
+ SET intervalstyle = postgres;
+ SET timezone = UTC;
+ SELECT dblink_connect('myconn','dbname=contrib_regression');
+ SELECT dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;');
+ 
+ -- The following attempt test various paths at which TupleDescs are
+ -- formed and inspected for containment of types requiring local GUC
+ -- setting.
+ 
+ -- single row synchronous case
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00'')) t')
+ 	 AS t(a timestamptz);
+ 
+ -- multi-row synchronous case
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00''),
+ 		   (''12.03.2013 00:00:00+00'')) t')
+ 	 AS t(a timestamptz);
+ 
+ -- single-row asynchronous case
+ SELECT *
+ FROM dblink_send_query('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00'')) t');
+ CREATE TEMPORARY TABLE result AS
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz));
+ SELECT * FROM result;
+ DROP TABLE result;
+ 
+ -- multi-row asynchronous case
+ SELECT *
+ FROM dblink_send_query('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00''),
+ 		   (''12.03.2013 00:00:00+00'')) t');
+ CREATE TEMPORARY TABLE result AS
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz));
+ SELECT * FROM result;
+ DROP TABLE result;
+ 
+ -- Try an ambiguous interval
+ SELECT dblink_exec('myconn', 'SET intervalstyle = sql_standard;');
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''-1 2:03:04'')) i')
+ 	 AS i(i interval);
+ 
+ -- Try swapping to another format to ensure the GUCs are tracked
+ -- properly through a change.
+ SELECT dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;');
+ CREATE TEMPORARY TABLE result (t timestamptz);
+ INSERT INTO result (SELECT *
+ 	   FROM dblink('myconn',
+ 			'SELECT * FROM
+ 		(VALUES (''12.03.2013 00:00:00+00'')) t')
+ 		AS t(a timestamptz));
+ SELECT dblink_exec('myconn', 'SET datestyle = ISO, MDY;');
+ INSERT INTO result (SELECT *
+ 	   FROM dblink('myconn',
+ 			'SELECT * FROM
+ 		(VALUES (''03.12.2013 00:00:00+00'')) t')
+ 		AS t(a timestamptz));
+ SELECT DISTINCT * FROM result;
+ DROP TABLE result;
+ 
+ SELECT dblink_disconnect('myconn');
+ 
+ -- Make sure that the local values have retained their value in spite
+ -- of shenanigans on the connection.
+ SHOW datestyle;
+ SHOW intervalstyle;
+ 
+ RESET datestyle;
+ RESET intervalstyle;
+ RESET timezone;
#95Daniel Farina
daniel@heroku.com
In reply to: Daniel Farina (#94)
1 attachment(s)
Re: postgres_fdw vs data formatting GUCs (was Re: [v9.3] writable foreign tables)

On Tue, Mar 19, 2013 at 6:10 PM, Daniel Farina <daniel@heroku.com> wrote:

On Tue, Mar 19, 2013 at 3:06 PM, Daniel Farina <daniel@heroku.com> wrote:

On Tue, Mar 19, 2013 at 2:41 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Daniel Farina <daniel@heroku.com> writes:

On Tue, Mar 19, 2013 at 1:16 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

I'd be inclined to eat the cost of calling PQparameterStatus every time
(which won't be that much) and instead try to optimize by avoiding the
GUC-setting overhead if the current value matches the local setting.
But even that might be premature optimization. Did you do any
performance testing to see if there was a problem worth avoiding?

Nope; should I invent a new way to do that, or would it be up to
commit standard based on inspection alone? I'm okay either way, let
me know.

Doesn't seem that hard to test: run a dblink query that pulls back a
bunch of data under best-case conditions (ie, local not remote server),
and time it with and without the proposed fix. If the difference
is marginal then it's not worth working hard to optimize.

Okay, will do, and here's the shorter and less mechanically intensive
naive version that I think is the baseline: it doesn't try to optimize
out any GUC settings and sets up the GUCs before the two
materialization paths in dblink.

The results. Summary: seems like grabbing the GUC and strcmp-ing is
worthwhile, but the amount of ping-ponging between processes adds some
noise to the timing results: utilization is far short of 100% on
either processor involved. Attached is a cumulative diff of the new
version, and also reproduced below are the changes to v2 that make up
v3.

I added programming around various NULL returns reading GUCs in this
revision, v4.

The non-cumulative changes:

--- a/contrib/dblink/dblink.c
+++ b/contrib/dblink/dblink.c
@@ -3005,8 +3005,22 @@ applyRemoteGucs(remoteGucs *rgs)
  /*
  * Attempt to avoid GUC setting if the remote and local GUCs
  * already have the same value.
+ *
+ * NB: Must error if the GUC is not found.
  */
- localVal = GetConfigOption(gucName, true, true);
+ localVal = GetConfigOption(gucName, false, true);
+
+ if (remoteVal == NULL)
+ ereport(ERROR,
+ (errmsg("could not load parameter status of %s",
+ gucName)));
+
+ /*
+ * An error must have been raised by now if GUC values could
+ * not be loaded for any reason.
+ */
+ Assert(localVal != NULL);
+ Assert(remoteVal != NULL);

if (strcmp(remoteVal, localVal) == 0)
continue;

--
fdr

Attachments:

dblink-guc-sensitive-types-v4.patchapplication/octet-stream; name=dblink-guc-sensitive-types-v4.patchDownload
dblink: perform local-GUC modification to parse GUC-sensitive types

Similar in purpose to cc3f281ffb0a5d9b187e7a7b7de4a045809ff683, but
taking into account that a dblink caller may choose to cause arbitrary
changes to DateStyle and IntervalStyle.  To handle this, it is
necessary to use PQparameterStatus before parsing any input, every
time.

*** a/contrib/dblink/dblink.c
--- b/contrib/dblink/dblink.c
***************
*** 53,58 ****
--- 53,59 ----
  #include "utils/acl.h"
  #include "utils/builtins.h"
  #include "utils/fmgroids.h"
+ #include "utils/guc.h"
  #include "utils/lsyscache.h"
  #include "utils/memutils.h"
  #include "utils/rel.h"
***************
*** 69,74 **** typedef struct remoteConn
--- 70,94 ----
  	bool		newXactForCursor;		/* Opened a transaction for a cursor */
  } remoteConn;
  
+ 
+ const char *parseAffectingGucs[] = {"DateStyle", "IntervalStyle"};
+ 
+ /*
+  * Contains information to save and restore GUCs for types with
+  * GUC-sensitive parsing.
+  */
+ typedef struct remoteGucs
+ {
+ 	/*
+ 	 * GUC nesting level.  Set to -1 if no GUC nesting level has been
+ 	 * introduced.
+ 	 */
+ 	int localGUCNestLevel;
+ 
+ 	/* Kept around for PQparameterStatus to interrogate remote GUCs */
+ 	PGconn *conn;
+ } remoteGucs;
+ 
  typedef struct storeInfo
  {
  	FunctionCallInfo fcinfo;
***************
*** 118,123 **** static void validate_pkattnums(Relation rel,
--- 138,146 ----
  				   int **pkattnums, int *pknumatts);
  static bool is_valid_dblink_option(const PQconninfoOption *options,
  					   const char *option, Oid context);
+ static void initRemoteGucs(remoteGucs *rgs, PGconn *conn);
+ static void applyRemoteGucs(remoteGucs *rgs);
+ static void restoreLocalGucs(remoteGucs *rgs);
  
  /* Global */
  static remoteConn *pconn = NULL;
***************
*** 531,536 **** dblink_fetch(PG_FUNCTION_ARGS)
--- 554,560 ----
  	char	   *curname = NULL;
  	int			howmany = 0;
  	bool		fail = true;	/* default to backward compatible */
+ 	remoteGucs	rgs;
  
  	prepTuplestoreResult(fcinfo);
  
***************
*** 605,611 **** dblink_fetch(PG_FUNCTION_ARGS)
  				 errmsg("cursor \"%s\" does not exist", curname)));
  	}
  
! 	materializeResult(fcinfo, res);
  	return (Datum) 0;
  }
  
--- 629,653 ----
  				 errmsg("cursor \"%s\" does not exist", curname)));
  	}
  
! 	/*
! 	 * Materialize the result, before doing so set GUCs that may
! 	 * affect parsing and then un-set them afterwards.
! 	 */
! 	initRemoteGucs(&rgs, conn);
! 
! 	PG_TRY();
! 	{
! 		applyRemoteGucs(&rgs);
! 		materializeResult(fcinfo, res);
! 	}
! 	PG_CATCH();
! 	{
! 		restoreLocalGucs(&rgs);
! 	}
! 	PG_END_TRY();
! 
! 	restoreLocalGucs(&rgs);
! 
  	return (Datum) 0;
  }
  
***************
*** 656,665 **** dblink_get_result(PG_FUNCTION_ARGS)
  static Datum
  dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
  {
! 	PGconn	   *volatile conn = NULL;
! 	volatile bool freeconn = false;
  
  	prepTuplestoreResult(fcinfo);
  
  	DBLINK_INIT;
  
--- 698,709 ----
  static Datum
  dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
  {
! 	PGconn	   *volatile		conn	 = NULL;
! 	volatile bool				freeconn = false;
! 	remoteGucs					rgs;
  
  	prepTuplestoreResult(fcinfo);
+ 	initRemoteGucs(&rgs, NULL);
  
  	DBLINK_INIT;
  
***************
*** 728,735 **** dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
--- 772,787 ----
  		if (!conn)
  			DBLINK_CONN_NOT_AVAIL;
  
+ 		initRemoteGucs(&rgs, conn);
+ 
  		if (!is_async)
  		{
+ 			/*
+ 			 * Before parsing input, synchronize local
+ 			 * type-parsing-affecting GUCs with the remote GUC value.
+ 			 */
+ 			applyRemoteGucs(&rgs);
+ 
  			/* synchronous query, use efficient tuple collection method */
  			materializeQueryResult(fcinfo, conn, conname, sql, fail);
  		}
***************
*** 750,755 **** dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
--- 802,814 ----
  				}
  				else
  				{
+ 					/*
+ 					 * Before parsing input, synchronize local
+ 					 * type-parsing-affecting GUCs with the remote GUC
+ 					 * value.
+ 					 */
+ 					applyRemoteGucs(&rgs);
+ 
  					materializeResult(fcinfo, res);
  				}
  			}
***************
*** 760,765 **** dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
--- 819,828 ----
  		/* if needed, close the connection to the database */
  		if (freeconn)
  			PQfinish(conn);
+ 
+ 		/* Pop any set GUCs, if necessary */
+ 		restoreLocalGucs(&rgs);
+ 
  		PG_RE_THROW();
  	}
  	PG_END_TRY();
***************
*** 768,773 **** dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
--- 831,839 ----
  	if (freeconn)
  		PQfinish(conn);
  
+ 	/* Pop any set GUCs, if necessary */
+ 	restoreLocalGucs(&rgs);
+ 
  	return (Datum) 0;
  }
  
***************
*** 2898,2900 **** is_valid_dblink_option(const PQconninfoOption *options, const char *option,
--- 2964,3063 ----
  
  	return true;
  }
+ 
+ /* Initializer for a "remoteGucs" struct value. */
+ static void
+ initRemoteGucs(remoteGucs *rgs, PGconn *conn)
+ {
+ 	rgs->localGUCNestLevel = -1;
+ 	rgs->conn			   = conn;
+ }
+ 
+ /*
+  * Scan a TupleDesc and, should it contain types that are sensitive to
+  * GUCs, acquire remote GUCs and set them in a new GUC nesting level.
+  * This is undone with restoreLocalGucs.
+  */
+ static void
+ applyRemoteGucs(remoteGucs *rgs)
+ {
+ 	const int numGucs = sizeof parseAffectingGucs / sizeof *parseAffectingGucs;
+ 
+ 	int i;
+ 	int addedGucNesting = false;
+ 
+ 	/*
+ 	 * Affected types require local GUC manipulations.  Create a new
+ 	 * GUC NestLevel to overlay the remote settings.
+ 	 *
+ 	 * Also, this nesting is done exactly once per remoteGucInfo
+ 	 * structure, so expect it to come with an invalid NestLevel.
+ 	 */
+ 	Assert(rgs->localGUCNestLevel == -1);
+ 
+ 	for (i = 0; i < numGucs; i += 1)
+ 	{
+ 		const char		*gucName   = parseAffectingGucs[i];
+ 		const char		*remoteVal = PQparameterStatus(rgs->conn, gucName);
+ 		const char		*localVal;
+ 		int				 gucApplyStatus;
+ 
+ 		/*
+ 		 * Attempt to avoid GUC setting if the remote and local GUCs
+ 		 * already have the same value.
+ 		 *
+ 		 * NB: Must error if the GUC is not found.
+ 		 */
+ 		localVal = GetConfigOption(gucName, false, true);
+ 
+ 		if (remoteVal == NULL)
+ 			ereport(ERROR,
+ 					(errmsg("could not load parameter status of %s",
+ 							gucName)));
+ 
+ 		/*
+ 		 * An error must have been raised by now if GUC values could
+ 		 * not be loaded for any reason.
+ 		 */
+ 		Assert(localVal != NULL);
+ 		Assert(remoteVal != NULL);
+ 
+ 		if (strcmp(remoteVal, localVal) == 0)
+ 			continue;
+ 
+ 		if (!addedGucNesting)
+ 		{
+ 			rgs->localGUCNestLevel = NewGUCNestLevel();
+ 			addedGucNesting = true;
+ 		}
+ 
+ 		gucApplyStatus = set_config_option(gucName, remoteVal,
+ 										   PGC_USERSET, PGC_S_SESSION,
+ 										   GUC_ACTION_SAVE, true, 0);
+ 		if (gucApplyStatus != 1)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("cannot load remote configuration %s "
+ 							"for type parsing",
+ 							gucName)));
+ 	}
+ }
+ 
+ /*
+  * Restore local GUCs after they have been overlaid with remote
+  * settings for type parsing, destroying the GUC nesting level.
+  */
+ static void
+ restoreLocalGucs(remoteGucs *rgs)
+ {
+ 	/*
+ 	 * A new GUCNestLevel was not introduced, so don't bother
+ 	 * restoring, either.
+ 	 */
+ 	if (rgs->localGUCNestLevel == -1)
+ 	{
+ 		return;
+ 	}
+ 
+ 	AtEOXact_GUC(false, rgs->localGUCNestLevel);
+ }
*** a/contrib/dblink/expected/dblink.out
--- b/contrib/dblink/expected/dblink.out
***************
*** 913,915 **** SELECT dblink_build_sql_delete('test_dropped', '1', 1,
--- 913,1077 ----
   DELETE FROM test_dropped WHERE id = '2'
  (1 row)
  
+ -- test the local mimicry of remote GUC values in parsing for affected
+ -- types
+ SET datestyle = ISO, MDY;
+ SET intervalstyle = postgres;
+ SET timezone = UTC;
+ SELECT dblink_connect('myconn','dbname=contrib_regression');
+  dblink_connect 
+ ----------------
+  OK
+ (1 row)
+ 
+ SELECT dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;');
+  dblink_exec 
+ -------------
+  SET
+ (1 row)
+ 
+ -- The following attempt test various paths at which TupleDescs are
+ -- formed and inspected for containment of types requiring local GUC
+ -- setting.
+ -- single row synchronous case
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00'')) t')
+ 	 AS t(a timestamptz);
+            a            
+ ------------------------
+  2013-03-12 00:00:00+00
+ (1 row)
+ 
+ -- multi-row synchronous case
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00''),
+ 		   (''12.03.2013 00:00:00+00'')) t')
+ 	 AS t(a timestamptz);
+            a            
+ ------------------------
+  2013-03-12 00:00:00+00
+  2013-03-12 00:00:00+00
+ (2 rows)
+ 
+ -- single-row asynchronous case
+ SELECT *
+ FROM dblink_send_query('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00'')) t');
+  dblink_send_query 
+ -------------------
+                  1
+ (1 row)
+ 
+ CREATE TEMPORARY TABLE result AS
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz));
+ SELECT * FROM result;
+            t            
+ ------------------------
+  2013-03-12 00:00:00+00
+ (1 row)
+ 
+ DROP TABLE result;
+ -- multi-row asynchronous case
+ SELECT *
+ FROM dblink_send_query('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00''),
+ 		   (''12.03.2013 00:00:00+00'')) t');
+  dblink_send_query 
+ -------------------
+                  1
+ (1 row)
+ 
+ CREATE TEMPORARY TABLE result AS
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz));
+ SELECT * FROM result;
+            t            
+ ------------------------
+  2013-03-12 00:00:00+00
+  2013-03-12 00:00:00+00
+ (2 rows)
+ 
+ DROP TABLE result;
+ -- Try an ambiguous interval
+ SELECT dblink_exec('myconn', 'SET intervalstyle = sql_standard;');
+  dblink_exec 
+ -------------
+  SET
+ (1 row)
+ 
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''-1 2:03:04'')) i')
+ 	 AS i(i interval);
+          i         
+ -------------------
+  -1 days -02:03:04
+ (1 row)
+ 
+ -- Try swapping to another format to ensure the GUCs are tracked
+ -- properly through a change.
+ SELECT dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;');
+  dblink_exec 
+ -------------
+  SET
+ (1 row)
+ 
+ CREATE TEMPORARY TABLE result (t timestamptz);
+ INSERT INTO result (SELECT *
+ 	   FROM dblink('myconn',
+ 			'SELECT * FROM
+ 		(VALUES (''12.03.2013 00:00:00+00'')) t')
+ 		AS t(a timestamptz));
+ SELECT dblink_exec('myconn', 'SET datestyle = ISO, MDY;');
+  dblink_exec 
+ -------------
+  SET
+ (1 row)
+ 
+ INSERT INTO result (SELECT *
+ 	   FROM dblink('myconn',
+ 			'SELECT * FROM
+ 		(VALUES (''03.12.2013 00:00:00+00'')) t')
+ 		AS t(a timestamptz));
+ SELECT DISTINCT * FROM result;
+            t            
+ ------------------------
+  2013-03-12 00:00:00+00
+ (1 row)
+ 
+ DROP TABLE result;
+ SELECT dblink_disconnect('myconn');
+  dblink_disconnect 
+ -------------------
+  OK
+ (1 row)
+ 
+ -- Make sure that the local values have retained their value in spite
+ -- of shenanigans on the connection.
+ SHOW datestyle;
+  DateStyle 
+ -----------
+  ISO, MDY
+ (1 row)
+ 
+ SHOW intervalstyle;
+  IntervalStyle 
+ ---------------
+  postgres
+ (1 row)
+ 
+ RESET datestyle;
+ RESET intervalstyle;
+ RESET timezone;
*** a/contrib/dblink/sql/dblink.sql
--- b/contrib/dblink/sql/dblink.sql
***************
*** 426,428 **** SELECT dblink_build_sql_update('test_dropped', '1', 1,
--- 426,519 ----
  
  SELECT dblink_build_sql_delete('test_dropped', '1', 1,
                                 ARRAY['2'::TEXT]);
+ 
+ -- test the local mimicry of remote GUC values in parsing for affected
+ -- types
+ SET datestyle = ISO, MDY;
+ SET intervalstyle = postgres;
+ SET timezone = UTC;
+ SELECT dblink_connect('myconn','dbname=contrib_regression');
+ SELECT dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;');
+ 
+ -- The following attempt test various paths at which TupleDescs are
+ -- formed and inspected for containment of types requiring local GUC
+ -- setting.
+ 
+ -- single row synchronous case
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00'')) t')
+ 	 AS t(a timestamptz);
+ 
+ -- multi-row synchronous case
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00''),
+ 		   (''12.03.2013 00:00:00+00'')) t')
+ 	 AS t(a timestamptz);
+ 
+ -- single-row asynchronous case
+ SELECT *
+ FROM dblink_send_query('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00'')) t');
+ CREATE TEMPORARY TABLE result AS
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz));
+ SELECT * FROM result;
+ DROP TABLE result;
+ 
+ -- multi-row asynchronous case
+ SELECT *
+ FROM dblink_send_query('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00''),
+ 		   (''12.03.2013 00:00:00+00'')) t');
+ CREATE TEMPORARY TABLE result AS
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz));
+ SELECT * FROM result;
+ DROP TABLE result;
+ 
+ -- Try an ambiguous interval
+ SELECT dblink_exec('myconn', 'SET intervalstyle = sql_standard;');
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''-1 2:03:04'')) i')
+ 	 AS i(i interval);
+ 
+ -- Try swapping to another format to ensure the GUCs are tracked
+ -- properly through a change.
+ SELECT dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;');
+ CREATE TEMPORARY TABLE result (t timestamptz);
+ INSERT INTO result (SELECT *
+ 	   FROM dblink('myconn',
+ 			'SELECT * FROM
+ 		(VALUES (''12.03.2013 00:00:00+00'')) t')
+ 		AS t(a timestamptz));
+ SELECT dblink_exec('myconn', 'SET datestyle = ISO, MDY;');
+ INSERT INTO result (SELECT *
+ 	   FROM dblink('myconn',
+ 			'SELECT * FROM
+ 		(VALUES (''03.12.2013 00:00:00+00'')) t')
+ 		AS t(a timestamptz));
+ SELECT DISTINCT * FROM result;
+ DROP TABLE result;
+ 
+ SELECT dblink_disconnect('myconn');
+ 
+ -- Make sure that the local values have retained their value in spite
+ -- of shenanigans on the connection.
+ SHOW datestyle;
+ SHOW intervalstyle;
+ 
+ RESET datestyle;
+ RESET intervalstyle;
+ RESET timezone;
#96Daniel Farina
daniel@heroku.com
In reply to: Daniel Farina (#95)
1 attachment(s)
Re: postgres_fdw vs data formatting GUCs (was Re: [v9.3] writable foreign tables)

On Tue, Mar 19, 2013 at 10:37 PM, Daniel Farina <daniel@heroku.com> wrote:

I added programming around various NULL returns reading GUCs in this
revision, v4.

Okay, one more of those fridge-logic bugs. Sorry for the noise. v5.

A missing PG_RETHROW to get the properly finally-esque semantics:

--- a/contrib/dblink/dblink.c
+++ b/contrib/dblink/dblink.c
@@ -642,7 +642,10 @@ dblink_fetch(PG_FUNCTION_ARGS)
  }
  PG_CATCH();
  {
+ /* Pop any set GUCs, if necessary */
  restoreLocalGucs(&rgs);
+
+ PG_RE_THROW();
  }
  PG_END_TRY();

This was easy to add a regression test to exercise, and so I did (not
displayed here).

--
fdr

Attachments:

dblink-guc-sensitive-types-v5.patchapplication/octet-stream; name=dblink-guc-sensitive-types-v5.patchDownload
dblink: perform local-GUC modification to parse GUC-sensitive types

Similar in purpose to cc3f281ffb0a5d9b187e7a7b7de4a045809ff683, but
taking into account that a dblink caller may choose to cause arbitrary
changes to DateStyle and IntervalStyle.  To handle this, it is
necessary to use PQparameterStatus before parsing any input, every
time.

*** a/contrib/dblink/dblink.c
--- b/contrib/dblink/dblink.c
***************
*** 53,58 ****
--- 53,59 ----
  #include "utils/acl.h"
  #include "utils/builtins.h"
  #include "utils/fmgroids.h"
+ #include "utils/guc.h"
  #include "utils/lsyscache.h"
  #include "utils/memutils.h"
  #include "utils/rel.h"
***************
*** 69,74 **** typedef struct remoteConn
--- 70,94 ----
  	bool		newXactForCursor;		/* Opened a transaction for a cursor */
  } remoteConn;
  
+ 
+ const char *parseAffectingGucs[] = {"DateStyle", "IntervalStyle"};
+ 
+ /*
+  * Contains information to save and restore GUCs for types with
+  * GUC-sensitive parsing.
+  */
+ typedef struct remoteGucs
+ {
+ 	/*
+ 	 * GUC nesting level.  Set to -1 if no GUC nesting level has been
+ 	 * introduced.
+ 	 */
+ 	int localGUCNestLevel;
+ 
+ 	/* Kept around for PQparameterStatus to interrogate remote GUCs */
+ 	PGconn *conn;
+ } remoteGucs;
+ 
  typedef struct storeInfo
  {
  	FunctionCallInfo fcinfo;
***************
*** 118,123 **** static void validate_pkattnums(Relation rel,
--- 138,146 ----
  				   int **pkattnums, int *pknumatts);
  static bool is_valid_dblink_option(const PQconninfoOption *options,
  					   const char *option, Oid context);
+ static void initRemoteGucs(remoteGucs *rgs, PGconn *conn);
+ static void applyRemoteGucs(remoteGucs *rgs);
+ static void restoreLocalGucs(remoteGucs *rgs);
  
  /* Global */
  static remoteConn *pconn = NULL;
***************
*** 531,536 **** dblink_fetch(PG_FUNCTION_ARGS)
--- 554,560 ----
  	char	   *curname = NULL;
  	int			howmany = 0;
  	bool		fail = true;	/* default to backward compatible */
+ 	remoteGucs	rgs;
  
  	prepTuplestoreResult(fcinfo);
  
***************
*** 605,611 **** dblink_fetch(PG_FUNCTION_ARGS)
  				 errmsg("cursor \"%s\" does not exist", curname)));
  	}
  
! 	materializeResult(fcinfo, res);
  	return (Datum) 0;
  }
  
--- 629,656 ----
  				 errmsg("cursor \"%s\" does not exist", curname)));
  	}
  
! 	/*
! 	 * Materialize the result, before doing so set GUCs that may
! 	 * affect parsing and then un-set them afterwards.
! 	 */
! 	initRemoteGucs(&rgs, conn);
! 
! 	PG_TRY();
! 	{
! 		applyRemoteGucs(&rgs);
! 		materializeResult(fcinfo, res);
! 	}
! 	PG_CATCH();
! 	{
! 		/* Pop any set GUCs, if necessary */
! 		restoreLocalGucs(&rgs);
! 
! 		PG_RE_THROW();
! 	}
! 	PG_END_TRY();
! 
! 	restoreLocalGucs(&rgs);
! 
  	return (Datum) 0;
  }
  
***************
*** 656,665 **** dblink_get_result(PG_FUNCTION_ARGS)
  static Datum
  dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
  {
! 	PGconn	   *volatile conn = NULL;
! 	volatile bool freeconn = false;
  
  	prepTuplestoreResult(fcinfo);
  
  	DBLINK_INIT;
  
--- 701,712 ----
  static Datum
  dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
  {
! 	PGconn	   *volatile		conn	 = NULL;
! 	volatile bool				freeconn = false;
! 	remoteGucs					rgs;
  
  	prepTuplestoreResult(fcinfo);
+ 	initRemoteGucs(&rgs, NULL);
  
  	DBLINK_INIT;
  
***************
*** 728,735 **** dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
--- 775,790 ----
  		if (!conn)
  			DBLINK_CONN_NOT_AVAIL;
  
+ 		initRemoteGucs(&rgs, conn);
+ 
  		if (!is_async)
  		{
+ 			/*
+ 			 * Before parsing input, synchronize local
+ 			 * type-parsing-affecting GUCs with the remote GUC value.
+ 			 */
+ 			applyRemoteGucs(&rgs);
+ 
  			/* synchronous query, use efficient tuple collection method */
  			materializeQueryResult(fcinfo, conn, conname, sql, fail);
  		}
***************
*** 750,755 **** dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
--- 805,817 ----
  				}
  				else
  				{
+ 					/*
+ 					 * Before parsing input, synchronize local
+ 					 * type-parsing-affecting GUCs with the remote GUC
+ 					 * value.
+ 					 */
+ 					applyRemoteGucs(&rgs);
+ 
  					materializeResult(fcinfo, res);
  				}
  			}
***************
*** 760,765 **** dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
--- 822,831 ----
  		/* if needed, close the connection to the database */
  		if (freeconn)
  			PQfinish(conn);
+ 
+ 		/* Pop any set GUCs, if necessary */
+ 		restoreLocalGucs(&rgs);
+ 
  		PG_RE_THROW();
  	}
  	PG_END_TRY();
***************
*** 768,773 **** dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
--- 834,842 ----
  	if (freeconn)
  		PQfinish(conn);
  
+ 	/* Pop any set GUCs, if necessary */
+ 	restoreLocalGucs(&rgs);
+ 
  	return (Datum) 0;
  }
  
***************
*** 2898,2900 **** is_valid_dblink_option(const PQconninfoOption *options, const char *option,
--- 2967,3066 ----
  
  	return true;
  }
+ 
+ /* Initializer for a "remoteGucs" struct value. */
+ static void
+ initRemoteGucs(remoteGucs *rgs, PGconn *conn)
+ {
+ 	rgs->localGUCNestLevel = -1;
+ 	rgs->conn			   = conn;
+ }
+ 
+ /*
+  * Scan a TupleDesc and, should it contain types that are sensitive to
+  * GUCs, acquire remote GUCs and set them in a new GUC nesting level.
+  * This is undone with restoreLocalGucs.
+  */
+ static void
+ applyRemoteGucs(remoteGucs *rgs)
+ {
+ 	const int numGucs = sizeof parseAffectingGucs / sizeof *parseAffectingGucs;
+ 
+ 	int i;
+ 	int addedGucNesting = false;
+ 
+ 	/*
+ 	 * Affected types require local GUC manipulations.  Create a new
+ 	 * GUC NestLevel to overlay the remote settings.
+ 	 *
+ 	 * Also, this nesting is done exactly once per remoteGucInfo
+ 	 * structure, so expect it to come with an invalid NestLevel.
+ 	 */
+ 	Assert(rgs->localGUCNestLevel == -1);
+ 
+ 	for (i = 0; i < numGucs; i += 1)
+ 	{
+ 		const char		*gucName   = parseAffectingGucs[i];
+ 		const char		*remoteVal = PQparameterStatus(rgs->conn, gucName);
+ 		const char		*localVal;
+ 		int				 gucApplyStatus;
+ 
+ 		/*
+ 		 * Attempt to avoid GUC setting if the remote and local GUCs
+ 		 * already have the same value.
+ 		 *
+ 		 * NB: Must error if the GUC is not found.
+ 		 */
+ 		localVal = GetConfigOption(gucName, false, true);
+ 
+ 		if (remoteVal == NULL)
+ 			ereport(ERROR,
+ 					(errmsg("could not load parameter status of %s",
+ 							gucName)));
+ 
+ 		/*
+ 		 * An error must have been raised by now if GUC values could
+ 		 * not be loaded for any reason.
+ 		 */
+ 		Assert(localVal != NULL);
+ 		Assert(remoteVal != NULL);
+ 
+ 		if (strcmp(remoteVal, localVal) == 0)
+ 			continue;
+ 
+ 		if (!addedGucNesting)
+ 		{
+ 			rgs->localGUCNestLevel = NewGUCNestLevel();
+ 			addedGucNesting = true;
+ 		}
+ 
+ 		gucApplyStatus = set_config_option(gucName, remoteVal,
+ 										   PGC_USERSET, PGC_S_SESSION,
+ 										   GUC_ACTION_SAVE, true, 0);
+ 		if (gucApplyStatus != 1)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("cannot load remote configuration %s "
+ 							"for type parsing",
+ 							gucName)));
+ 	}
+ }
+ 
+ /*
+  * Restore local GUCs after they have been overlaid with remote
+  * settings for type parsing, destroying the GUC nesting level.
+  */
+ static void
+ restoreLocalGucs(remoteGucs *rgs)
+ {
+ 	/*
+ 	 * A new GUCNestLevel was not introduced, so don't bother
+ 	 * restoring, either.
+ 	 */
+ 	if (rgs->localGUCNestLevel == -1)
+ 	{
+ 		return;
+ 	}
+ 
+ 	AtEOXact_GUC(false, rgs->localGUCNestLevel);
+ }
*** a/contrib/dblink/expected/dblink.out
--- b/contrib/dblink/expected/dblink.out
***************
*** 913,915 **** SELECT dblink_build_sql_delete('test_dropped', '1', 1,
--- 913,1096 ----
   DELETE FROM test_dropped WHERE id = '2'
  (1 row)
  
+ -- test the local mimicry of remote GUC values in parsing for affected
+ -- types
+ SET datestyle = ISO, MDY;
+ SET intervalstyle = postgres;
+ SET timezone = UTC;
+ SELECT dblink_connect('myconn','dbname=contrib_regression');
+  dblink_connect 
+ ----------------
+  OK
+ (1 row)
+ 
+ SELECT dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;');
+  dblink_exec 
+ -------------
+  SET
+ (1 row)
+ 
+ -- The following attempt test various paths at which TupleDescs are
+ -- formed and inspected for containment of types requiring local GUC
+ -- setting.
+ -- single row synchronous case
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00'')) t')
+ 	 AS t(a timestamptz);
+            a            
+ ------------------------
+  2013-03-12 00:00:00+00
+ (1 row)
+ 
+ -- multi-row synchronous case
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00''),
+ 		   (''12.03.2013 00:00:00+00'')) t')
+ 	 AS t(a timestamptz);
+            a            
+ ------------------------
+  2013-03-12 00:00:00+00
+  2013-03-12 00:00:00+00
+ (2 rows)
+ 
+ -- single-row asynchronous case
+ SELECT *
+ FROM dblink_send_query('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00'')) t');
+  dblink_send_query 
+ -------------------
+                  1
+ (1 row)
+ 
+ CREATE TEMPORARY TABLE result AS
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz));
+ SELECT * FROM result;
+            t            
+ ------------------------
+  2013-03-12 00:00:00+00
+ (1 row)
+ 
+ DROP TABLE result;
+ -- multi-row asynchronous case
+ SELECT *
+ FROM dblink_send_query('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00''),
+ 		   (''12.03.2013 00:00:00+00'')) t');
+  dblink_send_query 
+ -------------------
+                  1
+ (1 row)
+ 
+ CREATE TEMPORARY TABLE result AS
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz));
+ SELECT * FROM result;
+            t            
+ ------------------------
+  2013-03-12 00:00:00+00
+  2013-03-12 00:00:00+00
+ (2 rows)
+ 
+ DROP TABLE result;
+ -- Try an ambiguous interval
+ SELECT dblink_exec('myconn', 'SET intervalstyle = sql_standard;');
+  dblink_exec 
+ -------------
+  SET
+ (1 row)
+ 
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''-1 2:03:04'')) i')
+ 	 AS i(i interval);
+          i         
+ -------------------
+  -1 days -02:03:04
+ (1 row)
+ 
+ -- Try swapping to another format to ensure the GUCs are tracked
+ -- properly through a change.
+ SELECT dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;');
+  dblink_exec 
+ -------------
+  SET
+ (1 row)
+ 
+ CREATE TEMPORARY TABLE result (t timestamptz);
+ INSERT INTO result (SELECT *
+ 	   FROM dblink('myconn',
+ 			'SELECT * FROM
+ 		(VALUES (''12.03.2013 00:00:00+00'')) t')
+ 		AS t(a timestamptz));
+ SELECT dblink_exec('myconn', 'SET datestyle = ISO, MDY;');
+  dblink_exec 
+ -------------
+  SET
+ (1 row)
+ 
+ INSERT INTO result (SELECT *
+ 	   FROM dblink('myconn',
+ 			'SELECT * FROM
+ 		(VALUES (''03.12.2013 00:00:00+00'')) t')
+ 		AS t(a timestamptz));
+ SELECT DISTINCT * FROM result;
+            t            
+ ------------------------
+  2013-03-12 00:00:00+00
+ (1 row)
+ 
+ DROP TABLE result;
+ -- Check error throwing in dblink_fetch
+ SELECT dblink_open('myconn','error_cursor',
+        'SELECT * FROM (VALUES (''1''), (''not an int'')) AS t(text);');
+  dblink_open 
+ -------------
+  OK
+ (1 row)
+ 
+ SELECT *
+ FROM dblink_fetch('myconn','error_cursor', 1) AS t(i int);
+  i 
+ ---
+  1
+ (1 row)
+ 
+ SELECT *
+ FROM dblink_fetch('myconn','error_cursor', 1) AS t(i int);
+ ERROR:  invalid input syntax for integer: "not an int"
+ -- Make sure that the local values have retained their value in spite
+ -- of shenanigans on the connection.
+ SHOW datestyle;
+  DateStyle 
+ -----------
+  ISO, MDY
+ (1 row)
+ 
+ SHOW intervalstyle;
+  IntervalStyle 
+ ---------------
+  postgres
+ (1 row)
+ 
+ -- Clean up GUC-setting tests
+ SELECT dblink_disconnect('myconn');
+  dblink_disconnect 
+ -------------------
+  OK
+ (1 row)
+ 
+ RESET datestyle;
+ RESET intervalstyle;
+ RESET timezone;
*** a/contrib/dblink/sql/dblink.sql
--- b/contrib/dblink/sql/dblink.sql
***************
*** 426,428 **** SELECT dblink_build_sql_update('test_dropped', '1', 1,
--- 426,527 ----
  
  SELECT dblink_build_sql_delete('test_dropped', '1', 1,
                                 ARRAY['2'::TEXT]);
+ 
+ -- test the local mimicry of remote GUC values in parsing for affected
+ -- types
+ SET datestyle = ISO, MDY;
+ SET intervalstyle = postgres;
+ SET timezone = UTC;
+ SELECT dblink_connect('myconn','dbname=contrib_regression');
+ SELECT dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;');
+ 
+ -- The following attempt test various paths at which TupleDescs are
+ -- formed and inspected for containment of types requiring local GUC
+ -- setting.
+ 
+ -- single row synchronous case
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00'')) t')
+ 	 AS t(a timestamptz);
+ 
+ -- multi-row synchronous case
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00''),
+ 		   (''12.03.2013 00:00:00+00'')) t')
+ 	 AS t(a timestamptz);
+ 
+ -- single-row asynchronous case
+ SELECT *
+ FROM dblink_send_query('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00'')) t');
+ CREATE TEMPORARY TABLE result AS
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz));
+ SELECT * FROM result;
+ DROP TABLE result;
+ 
+ -- multi-row asynchronous case
+ SELECT *
+ FROM dblink_send_query('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00''),
+ 		   (''12.03.2013 00:00:00+00'')) t');
+ CREATE TEMPORARY TABLE result AS
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz));
+ SELECT * FROM result;
+ DROP TABLE result;
+ 
+ -- Try an ambiguous interval
+ SELECT dblink_exec('myconn', 'SET intervalstyle = sql_standard;');
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''-1 2:03:04'')) i')
+ 	 AS i(i interval);
+ 
+ -- Try swapping to another format to ensure the GUCs are tracked
+ -- properly through a change.
+ SELECT dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;');
+ CREATE TEMPORARY TABLE result (t timestamptz);
+ INSERT INTO result (SELECT *
+ 	   FROM dblink('myconn',
+ 			'SELECT * FROM
+ 		(VALUES (''12.03.2013 00:00:00+00'')) t')
+ 		AS t(a timestamptz));
+ SELECT dblink_exec('myconn', 'SET datestyle = ISO, MDY;');
+ INSERT INTO result (SELECT *
+ 	   FROM dblink('myconn',
+ 			'SELECT * FROM
+ 		(VALUES (''03.12.2013 00:00:00+00'')) t')
+ 		AS t(a timestamptz));
+ SELECT DISTINCT * FROM result;
+ DROP TABLE result;
+ 
+ -- Check error throwing in dblink_fetch
+ SELECT dblink_open('myconn','error_cursor',
+        'SELECT * FROM (VALUES (''1''), (''not an int'')) AS t(text);');
+ SELECT *
+ FROM dblink_fetch('myconn','error_cursor', 1) AS t(i int);
+ SELECT *
+ FROM dblink_fetch('myconn','error_cursor', 1) AS t(i int);
+ 
+ -- Make sure that the local values have retained their value in spite
+ -- of shenanigans on the connection.
+ SHOW datestyle;
+ SHOW intervalstyle;
+ 
+ -- Clean up GUC-setting tests
+ SELECT dblink_disconnect('myconn');
+ RESET datestyle;
+ RESET intervalstyle;
+ RESET timezone;
#97Tom Lane
tgl@sss.pgh.pa.us
In reply to: Daniel Farina (#96)
Re: postgres_fdw vs data formatting GUCs (was Re: [v9.3] writable foreign tables)

Daniel Farina <daniel@heroku.com> writes:

Okay, one more of those fridge-logic bugs. Sorry for the noise. v5.

A missing PG_RETHROW to get the properly finally-esque semantics:

--- a/contrib/dblink/dblink.c
+++ b/contrib/dblink/dblink.c
@@ -642,7 +642,10 @@ dblink_fetch(PG_FUNCTION_ARGS)
}
PG_CATCH();
{
+ /* Pop any set GUCs, if necessary */
restoreLocalGucs(&rgs);
+
+ PG_RE_THROW();
}
PG_END_TRY();

Um ... you shouldn't need a PG_TRY for that at all. guc.c will take
care of popping the values on transaction abort --- that's really rather
the whole point of having that mechanism.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#98Daniel Farina
daniel@heroku.com
In reply to: Tom Lane (#97)
1 attachment(s)
Re: postgres_fdw vs data formatting GUCs (was Re: [v9.3] writable foreign tables)

On Wed, Mar 20, 2013 at 7:43 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Daniel Farina <daniel@heroku.com> writes:

Okay, one more of those fridge-logic bugs. Sorry for the noise. v5.

A missing PG_RETHROW to get the properly finally-esque semantics:

--- a/contrib/dblink/dblink.c
+++ b/contrib/dblink/dblink.c
@@ -642,7 +642,10 @@ dblink_fetch(PG_FUNCTION_ARGS)
}
PG_CATCH();
{
+ /* Pop any set GUCs, if necessary */
restoreLocalGucs(&rgs);
+
+ PG_RE_THROW();
}
PG_END_TRY();

Um ... you shouldn't need a PG_TRY for that at all. guc.c will take
care of popping the values on transaction abort --- that's really rather
the whole point of having that mechanism.

Hmm, well, merely raising the error doesn't reset the GUCs, so I was
rather thinking that this was a good idea to compose more neatly in
the case of nested exception processing, e.g.:

PG_TRY();
{
PG_TRY();
{
elog(NOTICE, "pre: %s",
GetConfigOption("DateStyle", false, true));
materializeResult(fcinfo, res);
}
PG_CATCH();
{
elog(NOTICE, "inner catch: %s",
GetConfigOption("DateStyle", false, true));
PG_RE_THROW();
}
PG_END_TRY();
}
PG_CATCH();
{
elog(NOTICE, "outer catch: %s",
GetConfigOption("DateStyle", false, true));
restoreLocalGucs(&rgs);
elog(NOTICE, "restored: %s",
GetConfigOption("DateStyle", false, true));
PG_RE_THROW();
}
PG_END_TRY();

I don't think this paranoia is made in other call sites for AtEOXact,
so included is a version that takes the same stance. This one shows up
best with the whitespace-insensitive option for git:

--- a/contrib/dblink/dblink.c
+++ b/contrib/dblink/dblink.c
@@ -634,21 +634,8 @@ dblink_fetch(PG_FUNCTION_ARGS)
         * affect parsing and then un-set them afterwards.
         */
        initRemoteGucs(&rgs, conn);
-
-       PG_TRY();
-       {
        applyRemoteGucs(&rgs);
        materializeResult(fcinfo, res);
-       }
-       PG_CATCH();
-       {
-               /* Pop any set GUCs, if necessary */
-               restoreLocalGucs(&rgs);
-
-               PG_RE_THROW();
-       }
-       PG_END_TRY();
-
        restoreLocalGucs(&rgs);

return (Datum) 0;
@@ -823,9 +810,6 @@ dblink_record_internal(FunctionCallInfo fcinfo,
bool is_async)
if (freeconn)
PQfinish(conn);

- /* Pop any set GUCs, if necessary */
- restoreLocalGucs(&rgs);
-
PG_RE_THROW();
}
PG_END_TRY();

--
fdr

Attachments:

dblink-guc-sensitive-types-v6.patchapplication/octet-stream; name=dblink-guc-sensitive-types-v6.patchDownload
dblink: perform local-GUC modification to parse GUC-sensitive types

Similar in purpose to cc3f281ffb0a5d9b187e7a7b7de4a045809ff683, but
taking into account that a dblink caller may choose to cause arbitrary
changes to DateStyle and IntervalStyle.  To handle this, it is
necessary to use PQparameterStatus before parsing any input, every
time.

*** a/contrib/dblink/dblink.c
--- b/contrib/dblink/dblink.c
***************
*** 53,58 ****
--- 53,59 ----
  #include "utils/acl.h"
  #include "utils/builtins.h"
  #include "utils/fmgroids.h"
+ #include "utils/guc.h"
  #include "utils/lsyscache.h"
  #include "utils/memutils.h"
  #include "utils/rel.h"
***************
*** 69,74 **** typedef struct remoteConn
--- 70,94 ----
  	bool		newXactForCursor;		/* Opened a transaction for a cursor */
  } remoteConn;
  
+ 
+ const char *parseAffectingGucs[] = {"DateStyle", "IntervalStyle"};
+ 
+ /*
+  * Contains information to save and restore GUCs for types with
+  * GUC-sensitive parsing.
+  */
+ typedef struct remoteGucs
+ {
+ 	/*
+ 	 * GUC nesting level.  Set to -1 if no GUC nesting level has been
+ 	 * introduced.
+ 	 */
+ 	int localGUCNestLevel;
+ 
+ 	/* Kept around for PQparameterStatus to interrogate remote GUCs */
+ 	PGconn *conn;
+ } remoteGucs;
+ 
  typedef struct storeInfo
  {
  	FunctionCallInfo fcinfo;
***************
*** 118,123 **** static void validate_pkattnums(Relation rel,
--- 138,146 ----
  				   int **pkattnums, int *pknumatts);
  static bool is_valid_dblink_option(const PQconninfoOption *options,
  					   const char *option, Oid context);
+ static void initRemoteGucs(remoteGucs *rgs, PGconn *conn);
+ static void applyRemoteGucs(remoteGucs *rgs);
+ static void restoreLocalGucs(remoteGucs *rgs);
  
  /* Global */
  static remoteConn *pconn = NULL;
***************
*** 531,536 **** dblink_fetch(PG_FUNCTION_ARGS)
--- 554,560 ----
  	char	   *curname = NULL;
  	int			howmany = 0;
  	bool		fail = true;	/* default to backward compatible */
+ 	remoteGucs	rgs;
  
  	prepTuplestoreResult(fcinfo);
  
***************
*** 605,611 **** dblink_fetch(PG_FUNCTION_ARGS)
--- 629,643 ----
  				 errmsg("cursor \"%s\" does not exist", curname)));
  	}
  
+ 	/*
+ 	 * Materialize the result, before doing so set GUCs that may
+ 	 * affect parsing and then un-set them afterwards.
+ 	 */
+ 	initRemoteGucs(&rgs, conn);
+ 	applyRemoteGucs(&rgs);
  	materializeResult(fcinfo, res);
+ 	restoreLocalGucs(&rgs);
+ 
  	return (Datum) 0;
  }
  
***************
*** 656,665 **** dblink_get_result(PG_FUNCTION_ARGS)
  static Datum
  dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
  {
! 	PGconn	   *volatile conn = NULL;
! 	volatile bool freeconn = false;
  
  	prepTuplestoreResult(fcinfo);
  
  	DBLINK_INIT;
  
--- 688,699 ----
  static Datum
  dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
  {
! 	PGconn	   *volatile		conn	 = NULL;
! 	volatile bool				freeconn = false;
! 	remoteGucs					rgs;
  
  	prepTuplestoreResult(fcinfo);
+ 	initRemoteGucs(&rgs, NULL);
  
  	DBLINK_INIT;
  
***************
*** 728,735 **** dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
--- 762,777 ----
  		if (!conn)
  			DBLINK_CONN_NOT_AVAIL;
  
+ 		initRemoteGucs(&rgs, conn);
+ 
  		if (!is_async)
  		{
+ 			/*
+ 			 * Before parsing input, synchronize local
+ 			 * type-parsing-affecting GUCs with the remote GUC value.
+ 			 */
+ 			applyRemoteGucs(&rgs);
+ 
  			/* synchronous query, use efficient tuple collection method */
  			materializeQueryResult(fcinfo, conn, conname, sql, fail);
  		}
***************
*** 750,755 **** dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
--- 792,804 ----
  				}
  				else
  				{
+ 					/*
+ 					 * Before parsing input, synchronize local
+ 					 * type-parsing-affecting GUCs with the remote GUC
+ 					 * value.
+ 					 */
+ 					applyRemoteGucs(&rgs);
+ 
  					materializeResult(fcinfo, res);
  				}
  			}
***************
*** 760,765 **** dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
--- 809,815 ----
  		/* if needed, close the connection to the database */
  		if (freeconn)
  			PQfinish(conn);
+ 
  		PG_RE_THROW();
  	}
  	PG_END_TRY();
***************
*** 768,773 **** dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
--- 818,826 ----
  	if (freeconn)
  		PQfinish(conn);
  
+ 	/* Pop any set GUCs, if necessary */
+ 	restoreLocalGucs(&rgs);
+ 
  	return (Datum) 0;
  }
  
***************
*** 2898,2900 **** is_valid_dblink_option(const PQconninfoOption *options, const char *option,
--- 2951,3050 ----
  
  	return true;
  }
+ 
+ /* Initializer for a "remoteGucs" struct value. */
+ static void
+ initRemoteGucs(remoteGucs *rgs, PGconn *conn)
+ {
+ 	rgs->localGUCNestLevel = -1;
+ 	rgs->conn			   = conn;
+ }
+ 
+ /*
+  * Scan a TupleDesc and, should it contain types that are sensitive to
+  * GUCs, acquire remote GUCs and set them in a new GUC nesting level.
+  * This is undone with restoreLocalGucs.
+  */
+ static void
+ applyRemoteGucs(remoteGucs *rgs)
+ {
+ 	const int numGucs = sizeof parseAffectingGucs / sizeof *parseAffectingGucs;
+ 
+ 	int i;
+ 	int addedGucNesting = false;
+ 
+ 	/*
+ 	 * Affected types require local GUC manipulations.  Create a new
+ 	 * GUC NestLevel to overlay the remote settings.
+ 	 *
+ 	 * Also, this nesting is done exactly once per remoteGucInfo
+ 	 * structure, so expect it to come with an invalid NestLevel.
+ 	 */
+ 	Assert(rgs->localGUCNestLevel == -1);
+ 
+ 	for (i = 0; i < numGucs; i += 1)
+ 	{
+ 		const char		*gucName   = parseAffectingGucs[i];
+ 		const char		*remoteVal = PQparameterStatus(rgs->conn, gucName);
+ 		const char		*localVal;
+ 		int				 gucApplyStatus;
+ 
+ 		/*
+ 		 * Attempt to avoid GUC setting if the remote and local GUCs
+ 		 * already have the same value.
+ 		 *
+ 		 * NB: Must error if the GUC is not found.
+ 		 */
+ 		localVal = GetConfigOption(gucName, false, true);
+ 
+ 		if (remoteVal == NULL)
+ 			ereport(ERROR,
+ 					(errmsg("could not load parameter status of %s",
+ 							gucName)));
+ 
+ 		/*
+ 		 * An error must have been raised by now if GUC values could
+ 		 * not be loaded for any reason.
+ 		 */
+ 		Assert(localVal != NULL);
+ 		Assert(remoteVal != NULL);
+ 
+ 		if (strcmp(remoteVal, localVal) == 0)
+ 			continue;
+ 
+ 		if (!addedGucNesting)
+ 		{
+ 			rgs->localGUCNestLevel = NewGUCNestLevel();
+ 			addedGucNesting = true;
+ 		}
+ 
+ 		gucApplyStatus = set_config_option(gucName, remoteVal,
+ 										   PGC_USERSET, PGC_S_SESSION,
+ 										   GUC_ACTION_SAVE, true, 0);
+ 		if (gucApplyStatus != 1)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("cannot load remote configuration %s "
+ 							"for type parsing",
+ 							gucName)));
+ 	}
+ }
+ 
+ /*
+  * Restore local GUCs after they have been overlaid with remote
+  * settings for type parsing, destroying the GUC nesting level.
+  */
+ static void
+ restoreLocalGucs(remoteGucs *rgs)
+ {
+ 	/*
+ 	 * A new GUCNestLevel was not introduced, so don't bother
+ 	 * restoring, either.
+ 	 */
+ 	if (rgs->localGUCNestLevel == -1)
+ 	{
+ 		return;
+ 	}
+ 
+ 	AtEOXact_GUC(false, rgs->localGUCNestLevel);
+ }
*** a/contrib/dblink/expected/dblink.out
--- b/contrib/dblink/expected/dblink.out
***************
*** 913,915 **** SELECT dblink_build_sql_delete('test_dropped', '1', 1,
--- 913,1096 ----
   DELETE FROM test_dropped WHERE id = '2'
  (1 row)
  
+ -- test the local mimicry of remote GUC values in parsing for affected
+ -- types
+ SET datestyle = ISO, MDY;
+ SET intervalstyle = postgres;
+ SET timezone = UTC;
+ SELECT dblink_connect('myconn','dbname=contrib_regression');
+  dblink_connect 
+ ----------------
+  OK
+ (1 row)
+ 
+ SELECT dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;');
+  dblink_exec 
+ -------------
+  SET
+ (1 row)
+ 
+ -- The following attempt test various paths at which TupleDescs are
+ -- formed and inspected for containment of types requiring local GUC
+ -- setting.
+ -- single row synchronous case
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00'')) t')
+ 	 AS t(a timestamptz);
+            a            
+ ------------------------
+  2013-03-12 00:00:00+00
+ (1 row)
+ 
+ -- multi-row synchronous case
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00''),
+ 		   (''12.03.2013 00:00:00+00'')) t')
+ 	 AS t(a timestamptz);
+            a            
+ ------------------------
+  2013-03-12 00:00:00+00
+  2013-03-12 00:00:00+00
+ (2 rows)
+ 
+ -- single-row asynchronous case
+ SELECT *
+ FROM dblink_send_query('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00'')) t');
+  dblink_send_query 
+ -------------------
+                  1
+ (1 row)
+ 
+ CREATE TEMPORARY TABLE result AS
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz));
+ SELECT * FROM result;
+            t            
+ ------------------------
+  2013-03-12 00:00:00+00
+ (1 row)
+ 
+ DROP TABLE result;
+ -- multi-row asynchronous case
+ SELECT *
+ FROM dblink_send_query('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00''),
+ 		   (''12.03.2013 00:00:00+00'')) t');
+  dblink_send_query 
+ -------------------
+                  1
+ (1 row)
+ 
+ CREATE TEMPORARY TABLE result AS
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz));
+ SELECT * FROM result;
+            t            
+ ------------------------
+  2013-03-12 00:00:00+00
+  2013-03-12 00:00:00+00
+ (2 rows)
+ 
+ DROP TABLE result;
+ -- Try an ambiguous interval
+ SELECT dblink_exec('myconn', 'SET intervalstyle = sql_standard;');
+  dblink_exec 
+ -------------
+  SET
+ (1 row)
+ 
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''-1 2:03:04'')) i')
+ 	 AS i(i interval);
+          i         
+ -------------------
+  -1 days -02:03:04
+ (1 row)
+ 
+ -- Try swapping to another format to ensure the GUCs are tracked
+ -- properly through a change.
+ CREATE TEMPORARY TABLE result (t timestamptz);
+ SELECT dblink_exec('myconn', 'SET datestyle = ISO, MDY;');
+  dblink_exec 
+ -------------
+  SET
+ (1 row)
+ 
+ INSERT INTO result (SELECT *
+ 	   FROM dblink('myconn',
+ 			'SELECT * FROM
+ 		(VALUES (''03.12.2013 00:00:00+00'')) t')
+ 		AS t(a timestamptz));
+ SELECT dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;');
+  dblink_exec 
+ -------------
+  SET
+ (1 row)
+ 
+ INSERT INTO result (SELECT *
+ 	   FROM dblink('myconn',
+ 			'SELECT * FROM
+ 		(VALUES (''12.03.2013 00:00:00+00'')) t')
+ 		AS t(a timestamptz));
+ SELECT DISTINCT * FROM result;
+            t            
+ ------------------------
+  2013-03-12 00:00:00+00
+ (1 row)
+ 
+ DROP TABLE result;
+ -- Check error throwing in dblink_fetch
+ SELECT dblink_open('myconn','error_cursor',
+        'SELECT * FROM (VALUES (''1''), (''not an int'')) AS t(text);');
+  dblink_open 
+ -------------
+  OK
+ (1 row)
+ 
+ SELECT *
+ FROM dblink_fetch('myconn','error_cursor', 1) AS t(i int);
+  i 
+ ---
+  1
+ (1 row)
+ 
+ SELECT *
+ FROM dblink_fetch('myconn','error_cursor', 1) AS t(i int);
+ ERROR:  invalid input syntax for integer: "not an int"
+ -- Make sure that the local values have retained their value in spite
+ -- of shenanigans on the connection.
+ SHOW datestyle;
+  DateStyle 
+ -----------
+  ISO, MDY
+ (1 row)
+ 
+ SHOW intervalstyle;
+  IntervalStyle 
+ ---------------
+  postgres
+ (1 row)
+ 
+ -- Clean up GUC-setting tests
+ SELECT dblink_disconnect('myconn');
+  dblink_disconnect 
+ -------------------
+  OK
+ (1 row)
+ 
+ RESET datestyle;
+ RESET intervalstyle;
+ RESET timezone;
*** a/contrib/dblink/sql/dblink.sql
--- b/contrib/dblink/sql/dblink.sql
***************
*** 426,428 **** SELECT dblink_build_sql_update('test_dropped', '1', 1,
--- 426,531 ----
  
  SELECT dblink_build_sql_delete('test_dropped', '1', 1,
                                 ARRAY['2'::TEXT]);
+ 
+ -- test the local mimicry of remote GUC values in parsing for affected
+ -- types
+ SET datestyle = ISO, MDY;
+ SET intervalstyle = postgres;
+ SET timezone = UTC;
+ SELECT dblink_connect('myconn','dbname=contrib_regression');
+ SELECT dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;');
+ 
+ -- The following attempt test various paths at which TupleDescs are
+ -- formed and inspected for containment of types requiring local GUC
+ -- setting.
+ 
+ -- single row synchronous case
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00'')) t')
+ 	 AS t(a timestamptz);
+ 
+ -- multi-row synchronous case
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00''),
+ 		   (''12.03.2013 00:00:00+00'')) t')
+ 	 AS t(a timestamptz);
+ 
+ -- single-row asynchronous case
+ SELECT *
+ FROM dblink_send_query('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00'')) t');
+ CREATE TEMPORARY TABLE result AS
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz));
+ SELECT * FROM result;
+ DROP TABLE result;
+ 
+ -- multi-row asynchronous case
+ SELECT *
+ FROM dblink_send_query('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00''),
+ 		   (''12.03.2013 00:00:00+00'')) t');
+ CREATE TEMPORARY TABLE result AS
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz));
+ SELECT * FROM result;
+ DROP TABLE result;
+ 
+ -- Try an ambiguous interval
+ SELECT dblink_exec('myconn', 'SET intervalstyle = sql_standard;');
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''-1 2:03:04'')) i')
+ 	 AS i(i interval);
+ 
+ -- Try swapping to another format to ensure the GUCs are tracked
+ -- properly through a change.
+ CREATE TEMPORARY TABLE result (t timestamptz);
+ 
+ SELECT dblink_exec('myconn', 'SET datestyle = ISO, MDY;');
+ INSERT INTO result (SELECT *
+ 	   FROM dblink('myconn',
+ 			'SELECT * FROM
+ 		(VALUES (''03.12.2013 00:00:00+00'')) t')
+ 		AS t(a timestamptz));
+ 
+ SELECT dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;');
+ INSERT INTO result (SELECT *
+ 	   FROM dblink('myconn',
+ 			'SELECT * FROM
+ 		(VALUES (''12.03.2013 00:00:00+00'')) t')
+ 		AS t(a timestamptz));
+ 
+ SELECT DISTINCT * FROM result;
+ 
+ DROP TABLE result;
+ 
+ -- Check error throwing in dblink_fetch
+ SELECT dblink_open('myconn','error_cursor',
+        'SELECT * FROM (VALUES (''1''), (''not an int'')) AS t(text);');
+ SELECT *
+ FROM dblink_fetch('myconn','error_cursor', 1) AS t(i int);
+ SELECT *
+ FROM dblink_fetch('myconn','error_cursor', 1) AS t(i int);
+ 
+ -- Make sure that the local values have retained their value in spite
+ -- of shenanigans on the connection.
+ SHOW datestyle;
+ SHOW intervalstyle;
+ 
+ -- Clean up GUC-setting tests
+ SELECT dblink_disconnect('myconn');
+ RESET datestyle;
+ RESET intervalstyle;
+ RESET timezone;
#99Tom Lane
tgl@sss.pgh.pa.us
In reply to: Daniel Farina (#98)
Re: postgres_fdw vs data formatting GUCs (was Re: [v9.3] writable foreign tables)

Daniel Farina <daniel@heroku.com> writes:

On Wed, Mar 20, 2013 at 7:43 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Um ... you shouldn't need a PG_TRY for that at all. guc.c will take
care of popping the values on transaction abort --- that's really rather
the whole point of having that mechanism.

Hmm, well, merely raising the error doesn't reset the GUCs, so I was
rather thinking that this was a good idea to compose more neatly in
the case of nested exception processing, e.g.:

In general, we don't allow processing to resume after an error until
transaction or subtransaction abort cleanup has been done. It's true
that if you look at the GUC state in a PG_CATCH block, you'll see it
hasn't been reset yet, but that's not very relevant.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#100Daniel Farina
daniel@heroku.com
In reply to: Tom Lane (#99)
1 attachment(s)
Re: postgres_fdw vs data formatting GUCs (was Re: [v9.3] writable foreign tables)

This contains some edits to comments that referred to the obsolete and
bogus TupleDesc scanning. No mechanical alterations.

--- a/contrib/dblink/dblink.c
+++ b/contrib/dblink/dblink.c
@@ -2961,9 +2961,8 @@ initRemoteGucs(remoteGucs *rgs, PGconn *conn)
 }
 /*
- * Scan a TupleDesc and, should it contain types that are sensitive to
- * GUCs, acquire remote GUCs and set them in a new GUC nesting level.
- * This is undone with restoreLocalGucs.
+ * Acquire remote GUCs that may affect type parsing and set them in a
+ * new GUC nesting level.
  */
 static void
 applyRemoteGucs(remoteGucs *rgs)
@@ -2974,11 +2973,8 @@ applyRemoteGucs(remoteGucs *rgs)
        int addedGucNesting = false;
        /*
-        * Affected types require local GUC manipulations.  Create a new
-        * GUC NestLevel to overlay the remote settings.
-        *
-        * Also, this nesting is done exactly once per remoteGucInfo
-        * structure, so expect it to come with an invalid NestLevel.
+        * This nesting is done exactly once per remoteGucInfo structure,
+        * so expect it to come with an invalid NestLevel.
         */
        Assert(rgs->localGUCNestLevel == -1);
diff --git a/contrib/dblink/expected/dblink.out
b/contrib/dblink/expected/dblink.out
index 3946485..579664e 100644
--- a/contrib/dblink/expected/dblink.out
+++ b/contrib/dblink/expected/dblink.out
@@ -930,9 +930,8 @@ SELECT dblink_exec('myconn', 'SET datestyle =
GERMAN, DMY;');
  SET
 (1 row)
--- The following attempt test various paths at which TupleDescs are
--- formed and inspected for containment of types requiring local GUC
--- setting.
+-- The following attempt test various paths at which tuples are formed
+-- and inspected for containment of types requiring local GUC setting.
 -- single row synchronous case
 SELECT *
 FROM dblink('myconn',
diff --git a/contrib/dblink/sql/dblink.sql b/contrib/dblink/sql/dblink.sql
index de925eb..7ff43fd 100644
--- a/contrib/dblink/sql/dblink.sql
+++ b/contrib/dblink/sql/dblink.sql
@@ -435,9 +435,8 @@ SET timezone = UTC;
 SELECT dblink_connect('myconn','dbname=contrib_regression');
 SELECT dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;');
--- The following attempt test various paths at which TupleDescs are
--- formed and inspected for containment of types requiring local GUC
--- setting.
+-- The following attempt test various paths at which tuples are formed
+-- and inspected for containment of types requiring local GUC setting.

-- single row synchronous case
SELECT *

--
fdr

Attachments:

dblink-guc-sensitive-types-v7.patchapplication/octet-stream; name=dblink-guc-sensitive-types-v7.patchDownload
dblink: perform local-GUC modification to parse GUC-sensitive types

Similar in purpose to cc3f281ffb0a5d9b187e7a7b7de4a045809ff683, but
taking into account that a dblink caller may choose to cause arbitrary
changes to DateStyle and IntervalStyle.  To handle this, it is
necessary to use PQparameterStatus before parsing any input, every
time.

*** a/contrib/dblink/dblink.c
--- b/contrib/dblink/dblink.c
***************
*** 53,58 ****
--- 53,59 ----
  #include "utils/acl.h"
  #include "utils/builtins.h"
  #include "utils/fmgroids.h"
+ #include "utils/guc.h"
  #include "utils/lsyscache.h"
  #include "utils/memutils.h"
  #include "utils/rel.h"
***************
*** 69,74 **** typedef struct remoteConn
--- 70,94 ----
  	bool		newXactForCursor;		/* Opened a transaction for a cursor */
  } remoteConn;
  
+ 
+ const char *parseAffectingGucs[] = {"DateStyle", "IntervalStyle"};
+ 
+ /*
+  * Contains information to save and restore GUCs for types with
+  * GUC-sensitive parsing.
+  */
+ typedef struct remoteGucs
+ {
+ 	/*
+ 	 * GUC nesting level.  Set to -1 if no GUC nesting level has been
+ 	 * introduced.
+ 	 */
+ 	int localGUCNestLevel;
+ 
+ 	/* Kept around for PQparameterStatus to interrogate remote GUCs */
+ 	PGconn *conn;
+ } remoteGucs;
+ 
  typedef struct storeInfo
  {
  	FunctionCallInfo fcinfo;
***************
*** 118,123 **** static void validate_pkattnums(Relation rel,
--- 138,146 ----
  				   int **pkattnums, int *pknumatts);
  static bool is_valid_dblink_option(const PQconninfoOption *options,
  					   const char *option, Oid context);
+ static void initRemoteGucs(remoteGucs *rgs, PGconn *conn);
+ static void applyRemoteGucs(remoteGucs *rgs);
+ static void restoreLocalGucs(remoteGucs *rgs);
  
  /* Global */
  static remoteConn *pconn = NULL;
***************
*** 531,536 **** dblink_fetch(PG_FUNCTION_ARGS)
--- 554,560 ----
  	char	   *curname = NULL;
  	int			howmany = 0;
  	bool		fail = true;	/* default to backward compatible */
+ 	remoteGucs	rgs;
  
  	prepTuplestoreResult(fcinfo);
  
***************
*** 605,611 **** dblink_fetch(PG_FUNCTION_ARGS)
--- 629,643 ----
  				 errmsg("cursor \"%s\" does not exist", curname)));
  	}
  
+ 	/*
+ 	 * Materialize the result, before doing so set GUCs that may
+ 	 * affect parsing and then un-set them afterwards.
+ 	 */
+ 	initRemoteGucs(&rgs, conn);
+ 	applyRemoteGucs(&rgs);
  	materializeResult(fcinfo, res);
+ 	restoreLocalGucs(&rgs);
+ 
  	return (Datum) 0;
  }
  
***************
*** 656,665 **** dblink_get_result(PG_FUNCTION_ARGS)
  static Datum
  dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
  {
! 	PGconn	   *volatile conn = NULL;
! 	volatile bool freeconn = false;
  
  	prepTuplestoreResult(fcinfo);
  
  	DBLINK_INIT;
  
--- 688,699 ----
  static Datum
  dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
  {
! 	PGconn	   *volatile		conn	 = NULL;
! 	volatile bool				freeconn = false;
! 	remoteGucs					rgs;
  
  	prepTuplestoreResult(fcinfo);
+ 	initRemoteGucs(&rgs, NULL);
  
  	DBLINK_INIT;
  
***************
*** 728,735 **** dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
--- 762,777 ----
  		if (!conn)
  			DBLINK_CONN_NOT_AVAIL;
  
+ 		initRemoteGucs(&rgs, conn);
+ 
  		if (!is_async)
  		{
+ 			/*
+ 			 * Before parsing input, synchronize local
+ 			 * type-parsing-affecting GUCs with the remote GUC value.
+ 			 */
+ 			applyRemoteGucs(&rgs);
+ 
  			/* synchronous query, use efficient tuple collection method */
  			materializeQueryResult(fcinfo, conn, conname, sql, fail);
  		}
***************
*** 750,755 **** dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
--- 792,804 ----
  				}
  				else
  				{
+ 					/*
+ 					 * Before parsing input, synchronize local
+ 					 * type-parsing-affecting GUCs with the remote GUC
+ 					 * value.
+ 					 */
+ 					applyRemoteGucs(&rgs);
+ 
  					materializeResult(fcinfo, res);
  				}
  			}
***************
*** 760,765 **** dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
--- 809,815 ----
  		/* if needed, close the connection to the database */
  		if (freeconn)
  			PQfinish(conn);
+ 
  		PG_RE_THROW();
  	}
  	PG_END_TRY();
***************
*** 768,773 **** dblink_record_internal(FunctionCallInfo fcinfo, bool is_async)
--- 818,826 ----
  	if (freeconn)
  		PQfinish(conn);
  
+ 	/* Pop any set GUCs, if necessary */
+ 	restoreLocalGucs(&rgs);
+ 
  	return (Datum) 0;
  }
  
***************
*** 2898,2900 **** is_valid_dblink_option(const PQconninfoOption *options, const char *option,
--- 2951,3046 ----
  
  	return true;
  }
+ 
+ /* Initializer for a "remoteGucs" struct value. */
+ static void
+ initRemoteGucs(remoteGucs *rgs, PGconn *conn)
+ {
+ 	rgs->localGUCNestLevel = -1;
+ 	rgs->conn			   = conn;
+ }
+ 
+ /*
+  * Acquire remote GUCs that may affect type parsing and set them in a
+  * new GUC nesting level.
+  */
+ static void
+ applyRemoteGucs(remoteGucs *rgs)
+ {
+ 	const int numGucs = sizeof parseAffectingGucs / sizeof *parseAffectingGucs;
+ 
+ 	int i;
+ 	int addedGucNesting = false;
+ 
+ 	/*
+ 	 * This nesting is done exactly once per remoteGucInfo structure,
+ 	 * so expect it to come with an invalid NestLevel.
+ 	 */
+ 	Assert(rgs->localGUCNestLevel == -1);
+ 
+ 	for (i = 0; i < numGucs; i += 1)
+ 	{
+ 		const char		*gucName   = parseAffectingGucs[i];
+ 		const char		*remoteVal = PQparameterStatus(rgs->conn, gucName);
+ 		const char		*localVal;
+ 		int				 gucApplyStatus;
+ 
+ 		/*
+ 		 * Attempt to avoid GUC setting if the remote and local GUCs
+ 		 * already have the same value.
+ 		 *
+ 		 * NB: Must error if the GUC is not found.
+ 		 */
+ 		localVal = GetConfigOption(gucName, false, true);
+ 
+ 		if (remoteVal == NULL)
+ 			ereport(ERROR,
+ 					(errmsg("could not load parameter status of %s",
+ 							gucName)));
+ 
+ 		/*
+ 		 * An error must have been raised by now if GUC values could
+ 		 * not be loaded for any reason.
+ 		 */
+ 		Assert(localVal != NULL);
+ 		Assert(remoteVal != NULL);
+ 
+ 		if (strcmp(remoteVal, localVal) == 0)
+ 			continue;
+ 
+ 		if (!addedGucNesting)
+ 		{
+ 			rgs->localGUCNestLevel = NewGUCNestLevel();
+ 			addedGucNesting = true;
+ 		}
+ 
+ 		gucApplyStatus = set_config_option(gucName, remoteVal,
+ 										   PGC_USERSET, PGC_S_SESSION,
+ 										   GUC_ACTION_SAVE, true, 0);
+ 		if (gucApplyStatus != 1)
+ 			ereport(ERROR,
+ 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 					 errmsg("cannot load remote configuration %s "
+ 							"for type parsing",
+ 							gucName)));
+ 	}
+ }
+ 
+ /*
+  * Restore local GUCs after they have been overlaid with remote
+  * settings for type parsing, destroying the GUC nesting level.
+  */
+ static void
+ restoreLocalGucs(remoteGucs *rgs)
+ {
+ 	/*
+ 	 * A new GUCNestLevel was not introduced, so don't bother
+ 	 * restoring, either.
+ 	 */
+ 	if (rgs->localGUCNestLevel == -1)
+ 	{
+ 		return;
+ 	}
+ 
+ 	AtEOXact_GUC(false, rgs->localGUCNestLevel);
+ }
*** a/contrib/dblink/expected/dblink.out
--- b/contrib/dblink/expected/dblink.out
***************
*** 913,915 **** SELECT dblink_build_sql_delete('test_dropped', '1', 1,
--- 913,1095 ----
   DELETE FROM test_dropped WHERE id = '2'
  (1 row)
  
+ -- test the local mimicry of remote GUC values in parsing for affected
+ -- types
+ SET datestyle = ISO, MDY;
+ SET intervalstyle = postgres;
+ SET timezone = UTC;
+ SELECT dblink_connect('myconn','dbname=contrib_regression');
+  dblink_connect 
+ ----------------
+  OK
+ (1 row)
+ 
+ SELECT dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;');
+  dblink_exec 
+ -------------
+  SET
+ (1 row)
+ 
+ -- The following attempt test various paths at which tuples are formed
+ -- and inspected for containment of types requiring local GUC setting.
+ -- single row synchronous case
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00'')) t')
+ 	 AS t(a timestamptz);
+            a            
+ ------------------------
+  2013-03-12 00:00:00+00
+ (1 row)
+ 
+ -- multi-row synchronous case
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00''),
+ 		   (''12.03.2013 00:00:00+00'')) t')
+ 	 AS t(a timestamptz);
+            a            
+ ------------------------
+  2013-03-12 00:00:00+00
+  2013-03-12 00:00:00+00
+ (2 rows)
+ 
+ -- single-row asynchronous case
+ SELECT *
+ FROM dblink_send_query('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00'')) t');
+  dblink_send_query 
+ -------------------
+                  1
+ (1 row)
+ 
+ CREATE TEMPORARY TABLE result AS
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz));
+ SELECT * FROM result;
+            t            
+ ------------------------
+  2013-03-12 00:00:00+00
+ (1 row)
+ 
+ DROP TABLE result;
+ -- multi-row asynchronous case
+ SELECT *
+ FROM dblink_send_query('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00''),
+ 		   (''12.03.2013 00:00:00+00'')) t');
+  dblink_send_query 
+ -------------------
+                  1
+ (1 row)
+ 
+ CREATE TEMPORARY TABLE result AS
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz));
+ SELECT * FROM result;
+            t            
+ ------------------------
+  2013-03-12 00:00:00+00
+  2013-03-12 00:00:00+00
+ (2 rows)
+ 
+ DROP TABLE result;
+ -- Try an ambiguous interval
+ SELECT dblink_exec('myconn', 'SET intervalstyle = sql_standard;');
+  dblink_exec 
+ -------------
+  SET
+ (1 row)
+ 
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''-1 2:03:04'')) i')
+ 	 AS i(i interval);
+          i         
+ -------------------
+  -1 days -02:03:04
+ (1 row)
+ 
+ -- Try swapping to another format to ensure the GUCs are tracked
+ -- properly through a change.
+ CREATE TEMPORARY TABLE result (t timestamptz);
+ SELECT dblink_exec('myconn', 'SET datestyle = ISO, MDY;');
+  dblink_exec 
+ -------------
+  SET
+ (1 row)
+ 
+ INSERT INTO result (SELECT *
+ 	   FROM dblink('myconn',
+ 			'SELECT * FROM
+ 		(VALUES (''03.12.2013 00:00:00+00'')) t')
+ 		AS t(a timestamptz));
+ SELECT dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;');
+  dblink_exec 
+ -------------
+  SET
+ (1 row)
+ 
+ INSERT INTO result (SELECT *
+ 	   FROM dblink('myconn',
+ 			'SELECT * FROM
+ 		(VALUES (''12.03.2013 00:00:00+00'')) t')
+ 		AS t(a timestamptz));
+ SELECT DISTINCT * FROM result;
+            t            
+ ------------------------
+  2013-03-12 00:00:00+00
+ (1 row)
+ 
+ DROP TABLE result;
+ -- Check error throwing in dblink_fetch
+ SELECT dblink_open('myconn','error_cursor',
+        'SELECT * FROM (VALUES (''1''), (''not an int'')) AS t(text);');
+  dblink_open 
+ -------------
+  OK
+ (1 row)
+ 
+ SELECT *
+ FROM dblink_fetch('myconn','error_cursor', 1) AS t(i int);
+  i 
+ ---
+  1
+ (1 row)
+ 
+ SELECT *
+ FROM dblink_fetch('myconn','error_cursor', 1) AS t(i int);
+ ERROR:  invalid input syntax for integer: "not an int"
+ -- Make sure that the local values have retained their value in spite
+ -- of shenanigans on the connection.
+ SHOW datestyle;
+  DateStyle 
+ -----------
+  ISO, MDY
+ (1 row)
+ 
+ SHOW intervalstyle;
+  IntervalStyle 
+ ---------------
+  postgres
+ (1 row)
+ 
+ -- Clean up GUC-setting tests
+ SELECT dblink_disconnect('myconn');
+  dblink_disconnect 
+ -------------------
+  OK
+ (1 row)
+ 
+ RESET datestyle;
+ RESET intervalstyle;
+ RESET timezone;
*** a/contrib/dblink/sql/dblink.sql
--- b/contrib/dblink/sql/dblink.sql
***************
*** 426,428 **** SELECT dblink_build_sql_update('test_dropped', '1', 1,
--- 426,530 ----
  
  SELECT dblink_build_sql_delete('test_dropped', '1', 1,
                                 ARRAY['2'::TEXT]);
+ 
+ -- test the local mimicry of remote GUC values in parsing for affected
+ -- types
+ SET datestyle = ISO, MDY;
+ SET intervalstyle = postgres;
+ SET timezone = UTC;
+ SELECT dblink_connect('myconn','dbname=contrib_regression');
+ SELECT dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;');
+ 
+ -- The following attempt test various paths at which tuples are formed
+ -- and inspected for containment of types requiring local GUC setting.
+ 
+ -- single row synchronous case
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00'')) t')
+ 	 AS t(a timestamptz);
+ 
+ -- multi-row synchronous case
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00''),
+ 		   (''12.03.2013 00:00:00+00'')) t')
+ 	 AS t(a timestamptz);
+ 
+ -- single-row asynchronous case
+ SELECT *
+ FROM dblink_send_query('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00'')) t');
+ CREATE TEMPORARY TABLE result AS
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz));
+ SELECT * FROM result;
+ DROP TABLE result;
+ 
+ -- multi-row asynchronous case
+ SELECT *
+ FROM dblink_send_query('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''12.03.2013 00:00:00+00''),
+ 		   (''12.03.2013 00:00:00+00'')) t');
+ CREATE TEMPORARY TABLE result AS
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz))
+ UNION ALL
+ (SELECT * from dblink_get_result('myconn') as t(t timestamptz));
+ SELECT * FROM result;
+ DROP TABLE result;
+ 
+ -- Try an ambiguous interval
+ SELECT dblink_exec('myconn', 'SET intervalstyle = sql_standard;');
+ SELECT *
+ FROM dblink('myconn',
+ 	   'SELECT * FROM
+ 	   (VALUES (''-1 2:03:04'')) i')
+ 	 AS i(i interval);
+ 
+ -- Try swapping to another format to ensure the GUCs are tracked
+ -- properly through a change.
+ CREATE TEMPORARY TABLE result (t timestamptz);
+ 
+ SELECT dblink_exec('myconn', 'SET datestyle = ISO, MDY;');
+ INSERT INTO result (SELECT *
+ 	   FROM dblink('myconn',
+ 			'SELECT * FROM
+ 		(VALUES (''03.12.2013 00:00:00+00'')) t')
+ 		AS t(a timestamptz));
+ 
+ SELECT dblink_exec('myconn', 'SET datestyle = GERMAN, DMY;');
+ INSERT INTO result (SELECT *
+ 	   FROM dblink('myconn',
+ 			'SELECT * FROM
+ 		(VALUES (''12.03.2013 00:00:00+00'')) t')
+ 		AS t(a timestamptz));
+ 
+ SELECT DISTINCT * FROM result;
+ 
+ DROP TABLE result;
+ 
+ -- Check error throwing in dblink_fetch
+ SELECT dblink_open('myconn','error_cursor',
+        'SELECT * FROM (VALUES (''1''), (''not an int'')) AS t(text);');
+ SELECT *
+ FROM dblink_fetch('myconn','error_cursor', 1) AS t(i int);
+ SELECT *
+ FROM dblink_fetch('myconn','error_cursor', 1) AS t(i int);
+ 
+ -- Make sure that the local values have retained their value in spite
+ -- of shenanigans on the connection.
+ SHOW datestyle;
+ SHOW intervalstyle;
+ 
+ -- Clean up GUC-setting tests
+ SELECT dblink_disconnect('myconn');
+ RESET datestyle;
+ RESET intervalstyle;
+ RESET timezone;
#101Tom Lane
tgl@sss.pgh.pa.us
In reply to: Daniel Farina (#100)
Re: postgres_fdw vs data formatting GUCs (was Re: [v9.3] writable foreign tables)

Daniel Farina <daniel@heroku.com> writes:

This contains some edits to comments that referred to the obsolete and
bogus TupleDesc scanning. No mechanical alterations.

Applied with some substantial revisions. I didn't like where you'd put
the apply/restore calls, for one thing --- we need to wait to do the
applies until we have the PGresult in hand, else we might be applying
stale values of the remote's GUCs. Also, adding a call that could throw
errors right before materializeResult() won't do, because that would
result in leaking the PGresult on error. The struct for state seemed a
bit of a mess too, given that you couldn't always initialize it in one
place. (In hindsight I could have left that alone given where I ended
up putting the calls, but it didn't seem to be providing any useful
isolation.)

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#102Daniel Farina
daniel@heroku.com
In reply to: Tom Lane (#101)
Re: postgres_fdw vs data formatting GUCs (was Re: [v9.3] writable foreign tables)

On Fri, Mar 22, 2013 at 12:29 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Daniel Farina <daniel@heroku.com> writes:

This contains some edits to comments that referred to the obsolete and
bogus TupleDesc scanning. No mechanical alterations.

Applied with some substantial revisions. I didn't like where you'd put
the apply/restore calls, for one thing --- we need to wait to do the
applies until we have the PGresult in hand, else we might be applying
stale values of the remote's GUCs. Also, adding a call that could throw
errors right before materializeResult() won't do, because that would
result in leaking the PGresult on error.

Good catches.

The struct for state seemed a
bit of a mess too, given that you couldn't always initialize it in one
place.

Yeah, I had to give that up when pushing things around, unless I
wanted to push more state down. It used to be neater.

(In hindsight I could have left that alone given where I ended
up putting the calls, but it didn't seem to be providing any useful
isolation.)

I studied your commit.

Yeah, the idea I had was to try to avoid pushing down a loaded a value
as a PGconn into the lower level helper functions, but perhaps that
economy was false one after the modifications. Earlier versions used
to push down the RemoteGucs struct instead of a full-blown conn to
hint to the restricted purpose of that reference. By conceding to this
pushdown I think the struct could have remained, as you said, but the
difference to clarity is likely marginal. I thought I found a way to
not have to widen the parameter list at all, so I preferred that one,
but clearly it is wrong, w.r.t. leaks and the not up-to-date protocol
state.

Sorry you had to root around so much in there to get something you
liked, but thanks for going through it.

--
fdr

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers