pgsql-v9.3-writable-fdw-poc.v4.patch

application/octet-stream

Filename: pgsql-v9.3-writable-fdw-poc.v4.patch
Type: application/octet-stream
Part: 0
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+
contrib/file_fdw/file_fdw.c 172 5
src/backend/commands/trigger.c 8 4
src/backend/executor/execMain.c 30 4
src/backend/executor/nodeForeignscan.c 135 1
src/backend/executor/nodeModifyTable.c 119 23
src/backend/nodes/copyfuncs.c 1 0
src/backend/nodes/outfuncs.c 1 0
src/backend/optimizer/plan/createplan.c 19 0
src/backend/optimizer/plan/initsplan.c 5 5
src/backend/optimizer/plan/planmain.c 1 1
src/backend/optimizer/plan/planner.c 1 1
src/backend/optimizer/prep/prepunion.c 2 1
src/backend/optimizer/util/plancat.c 22 1
src/backend/optimizer/util/relnode.c 4 3
src/backend/rewrite/rewriteHandler.c 45 10
src/include/commands/trigger.h 4 4
src/include/foreign/fdwapi.h 25 0
src/include/nodes/execnodes.h 10 3
src/include/nodes/plannodes.h 1 0
src/include/optimizer/pathnode.h 1 1
src/include/optimizer/plancat.h 1 1
src/include/optimizer/planmain.h 2 1
 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);