pgsql-v9.3-writable-fdw-poc.v9.part-1.patch

application/octet-stream

Filename: pgsql-v9.3-writable-fdw-poc.v9.part-1.patch
Type: application/octet-stream
Part: 1
Message: Re: [v9.3] writable foreign tables

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: unified
Series: patch v9
File+
doc/src/sgml/ddl.sgml 0 5
doc/src/sgml/fdwhandler.sgml 158 1
src/backend/executor/execMain.c 30 4
src/backend/executor/nodeForeignscan.c 135 1
src/backend/executor/nodeModifyTable.c 129 24
src/backend/foreign/foreign.c 68 0
src/backend/nodes/copyfuncs.c 2 0
src/backend/nodes/outfuncs.c 2 0
src/backend/optimizer/plan/createplan.c 47 1
src/backend/optimizer/plan/initsplan.c 5 5
src/backend/optimizer/plan/planmain.c 4 1
src/backend/optimizer/plan/planner.c 5 3
src/backend/optimizer/prep/prepunion.c 2 1
src/backend/optimizer/util/plancat.c 26 1
src/backend/optimizer/util/relnode.c 4 3
src/backend/parser/parse_relation.c 14 1
src/backend/rewrite/rewriteHandler.c 45 10
src/include/foreign/fdwapi.h 31 0
src/include/foreign/foreign.h 6 0
src/include/nodes/execnodes.h 10 3
src/include/nodes/plannodes.h 2 0
src/include/optimizer/pathnode.h 1 1
src/include/optimizer/plancat.h 1 1
src/include/optimizer/planmain.h 4 2
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);