summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTom Lane2013-03-10 18:14:53 +0000
committerTom Lane2013-03-10 18:16:02 +0000
commit21734d2fb896e0ecdddd3251caa72a3576e2d415 (patch)
treeaed4ee5509e618c0fd9746c8be17c5bf23a08a3f /src
parent7f49a67f954db3e92fd96963169fb8302959576e (diff)
Support writable foreign tables.
This patch adds the core-system infrastructure needed to support updates on foreign tables, and extends contrib/postgres_fdw to allow updates against remote Postgres servers. There's still a great deal of room for improvement in optimization of remote updates, but at least there's basic functionality there now. KaiGai Kohei, reviewed by Alexander Korotkov and Laurenz Albe, and rather heavily revised by Tom Lane.
Diffstat (limited to 'src')
-rw-r--r--src/backend/commands/copy.c15
-rw-r--r--src/backend/commands/explain.c35
-rw-r--r--src/backend/executor/execMain.c42
-rw-r--r--src/backend/executor/nodeForeignscan.c3
-rw-r--r--src/backend/executor/nodeModifyTable.c151
-rw-r--r--src/backend/nodes/copyfuncs.c1
-rw-r--r--src/backend/nodes/outfuncs.c1
-rw-r--r--src/backend/optimizer/plan/createplan.c54
-rw-r--r--src/backend/optimizer/plan/planner.c15
-rw-r--r--src/backend/optimizer/prep/preptlist.c3
-rw-r--r--src/backend/parser/analyze.c13
-rw-r--r--src/backend/rewrite/rewriteHandler.c27
-rw-r--r--src/include/foreign/fdwapi.h69
-rw-r--r--src/include/nodes/execnodes.h7
-rw-r--r--src/include/nodes/plannodes.h38
-rw-r--r--src/include/optimizer/planmain.h3
16 files changed, 399 insertions, 78 deletions
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 4825bca363f..4eb94a43add 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2121,17 +2121,10 @@ CopyFrom(CopyState cstate)
* here that basically duplicated execUtils.c ...)
*/
resultRelInfo = makeNode(ResultRelInfo);
- resultRelInfo->ri_RangeTableIndex = 1; /* dummy */
- resultRelInfo->ri_RelationDesc = cstate->rel;
- resultRelInfo->ri_TrigDesc = CopyTriggerDesc(cstate->rel->trigdesc);
- if (resultRelInfo->ri_TrigDesc)
- {
- resultRelInfo->ri_TrigFunctions = (FmgrInfo *)
- palloc0(resultRelInfo->ri_TrigDesc->numtriggers * sizeof(FmgrInfo));
- resultRelInfo->ri_TrigWhenExprs = (List **)
- palloc0(resultRelInfo->ri_TrigDesc->numtriggers * sizeof(List *));
- }
- resultRelInfo->ri_TrigInstrument = NULL;
+ InitResultRelInfo(resultRelInfo,
+ cstate->rel,
+ 1, /* dummy rangetable index */
+ 0);
ExecOpenIndices(resultRelInfo);
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 989b52da9d4..9799e9ecb41 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -90,6 +90,7 @@ static void ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir,
static void ExplainScanTarget(Scan *plan, ExplainState *es);
static void ExplainModifyTarget(ModifyTable *plan, ExplainState *es);
static void ExplainTargetRel(Plan *plan, Index rti, ExplainState *es);
+static void show_modifytable_info(ModifyTableState *mtstate, ExplainState *es);
static void ExplainMemberNodes(List *plans, PlanState **planstates,
List *ancestors, ExplainState *es);
static void ExplainSubPlans(List *plans, List *ancestors,
@@ -1341,6 +1342,9 @@ ExplainNode(PlanState *planstate, List *ancestors,
show_instrumentation_count("Rows Removed by Filter", 1,
planstate, es);
break;
+ case T_ModifyTable:
+ show_modifytable_info((ModifyTableState *) planstate, es);
+ break;
case T_Hash:
show_hash_info((HashState *) planstate, es);
break;
@@ -1840,7 +1844,8 @@ show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es)
FdwRoutine *fdwroutine = fsstate->fdwroutine;
/* Let the FDW emit whatever fields it wants */
- fdwroutine->ExplainForeignScan(fsstate, es);
+ if (fdwroutine->ExplainForeignScan != NULL)
+ fdwroutine->ExplainForeignScan(fsstate, es);
}
/*
@@ -2037,6 +2042,34 @@ ExplainTargetRel(Plan *plan, Index rti, ExplainState *es)
}
/*
+ * Show extra information for a ModifyTable node
+ */
+static void
+show_modifytable_info(ModifyTableState *mtstate, ExplainState *es)
+{
+ FdwRoutine *fdwroutine = mtstate->resultRelInfo->ri_FdwRoutine;
+
+ /*
+ * If the first target relation is a foreign table, call its FDW to
+ * display whatever additional fields it wants to. For now, we ignore the
+ * possibility of other targets being foreign tables, although the API for
+ * ExplainForeignModify is designed to allow them to be processed.
+ */
+ if (fdwroutine != NULL &&
+ fdwroutine->ExplainForeignModify != NULL)
+ {
+ ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
+ List *fdw_private = (List *) linitial(node->fdwPrivLists);
+
+ fdwroutine->ExplainForeignModify(mtstate,
+ mtstate->resultRelInfo,
+ fdw_private,
+ 0,
+ es);
+ }
+}
+
+/*
* Explain the constituent plans of a ModifyTable, Append, MergeAppend,
* BitmapAnd, or BitmapOr node.
*
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 288b29e44a9..1f2a23bcdd6 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -44,6 +44,7 @@
#include "catalog/namespace.h"
#include "commands/trigger.h"
#include "executor/execdebug.h"
+#include "foreign/fdwapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "optimizer/clauses.h"
@@ -1005,6 +1006,7 @@ void
CheckValidResultRel(Relation resultRel, CmdType operation)
{
TriggerDesc *trigDesc = resultRel->trigdesc;
+ FdwRoutine *fdwroutine;
switch (resultRel->rd_rel->relkind)
{
@@ -1069,10 +1071,35 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
RelationGetRelationName(resultRel))));
break;
case RELKIND_FOREIGN_TABLE:
- ereport(ERROR,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("cannot change foreign table \"%s\"",
- RelationGetRelationName(resultRel))));
+ /* Okay only if the FDW supports it */
+ fdwroutine = GetFdwRoutineForRelation(resultRel, false);
+ switch (operation)
+ {
+ case CMD_INSERT:
+ if (fdwroutine->ExecForeignInsert == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot insert into foreign table \"%s\"",
+ RelationGetRelationName(resultRel))));
+ break;
+ case CMD_UPDATE:
+ if (fdwroutine->ExecForeignUpdate == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot update foreign table \"%s\"",
+ RelationGetRelationName(resultRel))));
+ break;
+ case CMD_DELETE:
+ if (fdwroutine->ExecForeignDelete == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot delete from foreign table \"%s\"",
+ RelationGetRelationName(resultRel))));
+ break;
+ default:
+ elog(ERROR, "unrecognized CmdType: %d", (int) operation);
+ break;
+ }
break;
default:
ereport(ERROR,
@@ -1126,7 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
RelationGetRelationName(rel))));
break;
case RELKIND_FOREIGN_TABLE:
- /* Perhaps we can support this someday, but not today */
+ /* Should not get here */
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot lock rows in foreign table \"%s\"",
@@ -1180,6 +1207,11 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
resultRelInfo->ri_TrigWhenExprs = NULL;
resultRelInfo->ri_TrigInstrument = NULL;
}
+ if (resultRelationDesc->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ resultRelInfo->ri_FdwRoutine = GetFdwRoutineForRelation(resultRelationDesc, true);
+ else
+ resultRelInfo->ri_FdwRoutine = NULL;
+ resultRelInfo->ri_FdwState = NULL;
resultRelInfo->ri_ConstraintExprs = NULL;
resultRelInfo->ri_junkFilter = NULL;
resultRelInfo->ri_projectReturning = NULL;
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 63478cd12ad..448fd6a912f 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -147,7 +147,8 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
scanstate->ss.ss_currentRelation = currentRelation;
/*
- * get the scan type from the relation descriptor.
+ * get the scan type from the relation descriptor. (XXX at some point we
+ * might want to let the FDW editorialize on the scan tupdesc.)
*/
ExecAssignScanType(&scanstate->ss, RelationGetDescr(currentRelation));
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index cb084d03d47..a6f247e1bc3 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
#include "commands/trigger.h"
#include "executor/executor.h"
#include "executor/nodeModifyTable.h"
+#include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "storage/bufmgr.h"
@@ -225,6 +226,24 @@ ExecInsert(TupleTableSlot *slot,
newId = InvalidOid;
}
+ else if (resultRelInfo->ri_FdwRoutine)
+ {
+ /*
+ * insert into foreign table: let the FDW do it
+ */
+ slot = resultRelInfo->ri_FdwRoutine->ExecForeignInsert(estate,
+ resultRelInfo,
+ slot,
+ planSlot);
+
+ if (slot == NULL) /* "do nothing" */
+ return NULL;
+
+ /* FDW might have changed tuple */
+ tuple = ExecMaterializeSlot(slot);
+
+ newId = InvalidOid;
+ }
else
{
/*
@@ -279,7 +298,9 @@ ExecInsert(TupleTableSlot *slot,
* When deleting from a table, tupleid identifies the tuple to
* delete and oldtuple is NULL. When deleting from a view,
* oldtuple is passed to the INSTEAD OF triggers and identifies
- * what to delete, and tupleid is invalid.
+ * what to delete, and tupleid is invalid. When deleting from a
+ * foreign table, both tupleid and oldtuple are NULL; the FDW has
+ * to figure out which row to delete using data from the planSlot.
*
* Returns RETURNING result if any, otherwise NULL.
* ----------------------------------------------------------------
@@ -296,6 +317,7 @@ ExecDelete(ItemPointer tupleid,
Relation resultRelationDesc;
HTSU_Result result;
HeapUpdateFailureData hufd;
+ TupleTableSlot *slot = NULL;
/*
* get information on the (current) result relation
@@ -334,6 +356,27 @@ ExecDelete(ItemPointer tupleid,
if (!dodelete) /* "do nothing" */
return NULL;
}
+ else if (resultRelInfo->ri_FdwRoutine)
+ {
+ /*
+ * delete from foreign table: let the FDW do it
+ *
+ * We offer the trigger tuple slot as a place to store RETURNING data,
+ * although the FDW can return some other slot if it wants. Set up
+ * the slot's tupdesc so the FDW doesn't need to do that for itself.
+ */
+ slot = estate->es_trig_tuple_slot;
+ if (slot->tts_tupleDescriptor != RelationGetDescr(resultRelationDesc))
+ ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc));
+
+ slot = resultRelInfo->ri_FdwRoutine->ExecForeignDelete(estate,
+ resultRelInfo,
+ slot,
+ planSlot);
+
+ if (slot == NULL) /* "do nothing" */
+ return NULL;
+ }
else
{
/*
@@ -443,34 +486,49 @@ ldelete:;
* We have to put the target tuple into a slot, which means first we
* gotta fetch it. We can use the trigger tuple slot.
*/
- TupleTableSlot *slot = estate->es_trig_tuple_slot;
TupleTableSlot *rslot;
HeapTupleData deltuple;
Buffer delbuffer;
- if (oldtuple != NULL)
+ if (resultRelInfo->ri_FdwRoutine)
{
- deltuple.t_data = oldtuple;
- deltuple.t_len = HeapTupleHeaderGetDatumLength(oldtuple);
- ItemPointerSetInvalid(&(deltuple.t_self));
- deltuple.t_tableOid = InvalidOid;
+ /* FDW must have provided a slot containing the deleted row */
+ Assert(!TupIsNull(slot));
delbuffer = InvalidBuffer;
}
else
{
- deltuple.t_self = *tupleid;
- if (!heap_fetch(resultRelationDesc, SnapshotAny,
- &deltuple, &delbuffer, false, NULL))
- elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
- }
+ slot = estate->es_trig_tuple_slot;
+ if (oldtuple != NULL)
+ {
+ deltuple.t_data = oldtuple;
+ deltuple.t_len = HeapTupleHeaderGetDatumLength(oldtuple);
+ ItemPointerSetInvalid(&(deltuple.t_self));
+ deltuple.t_tableOid = InvalidOid;
+ delbuffer = InvalidBuffer;
+ }
+ else
+ {
+ deltuple.t_self = *tupleid;
+ if (!heap_fetch(resultRelationDesc, SnapshotAny,
+ &deltuple, &delbuffer, false, NULL))
+ elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
+ }
- if (slot->tts_tupleDescriptor != RelationGetDescr(resultRelationDesc))
- ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc));
- ExecStoreTuple(&deltuple, slot, InvalidBuffer, false);
+ if (slot->tts_tupleDescriptor != RelationGetDescr(resultRelationDesc))
+ ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc));
+ ExecStoreTuple(&deltuple, slot, InvalidBuffer, false);
+ }
rslot = ExecProcessReturning(resultRelInfo->ri_projectReturning,
slot, planSlot);
+ /*
+ * Before releasing the target tuple again, make sure rslot has a
+ * local copy of any pass-by-reference values.
+ */
+ ExecMaterializeSlot(rslot);
+
ExecClearTuple(slot);
if (BufferIsValid(delbuffer))
ReleaseBuffer(delbuffer);
@@ -494,7 +552,9 @@ ldelete:;
* When updating a table, tupleid identifies the tuple to
* update and oldtuple is NULL. When updating a view, oldtuple
* is passed to the INSTEAD OF triggers and identifies what to
- * update, and tupleid is invalid.
+ * update, and tupleid is invalid. When updating a foreign table,
+ * both tupleid and oldtuple are NULL; the FDW has to figure out
+ * which row to update using data from the planSlot.
*
* Returns RETURNING result if any, otherwise NULL.
* ----------------------------------------------------------------
@@ -568,6 +628,22 @@ ExecUpdate(ItemPointer tupleid,
/* trigger might have changed tuple */
tuple = ExecMaterializeSlot(slot);
}
+ else if (resultRelInfo->ri_FdwRoutine)
+ {
+ /*
+ * update in foreign table: let the FDW do it
+ */
+ slot = resultRelInfo->ri_FdwRoutine->ExecForeignUpdate(estate,
+ resultRelInfo,
+ slot,
+ planSlot);
+
+ if (slot == NULL) /* "do nothing" */
+ return NULL;
+
+ /* FDW might have changed tuple */
+ tuple = ExecMaterializeSlot(slot);
+ }
else
{
LockTupleMode lockmode;
@@ -867,10 +943,12 @@ ExecModifyTable(ModifyTableState *node)
*/
if (operation == CMD_UPDATE || operation == CMD_DELETE)
{
+ char relkind;
Datum datum;
bool isNull;
- if (resultRelInfo->ri_RelationDesc->rd_rel->relkind == RELKIND_RELATION)
+ relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
+ if (relkind == RELKIND_RELATION)
{
datum = ExecGetJunkAttribute(slot,
junkfilter->jf_junkAttNo,
@@ -884,6 +962,10 @@ ExecModifyTable(ModifyTableState *node)
* ctid!! */
tupleid = &tuple_ctid;
}
+ else if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /* do nothing; FDW must fetch any junk attrs it wants */
+ }
else
{
datum = ExecGetJunkAttribute(slot,
@@ -1026,6 +1108,19 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
estate->es_result_relation_info = resultRelInfo;
mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
+ /* Also let FDWs init themselves for foreign-table result rels */
+ if (resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
+ {
+ List *fdw_private = (List *) list_nth(node->fdwPrivLists, i);
+
+ resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
+ resultRelInfo,
+ fdw_private,
+ i,
+ eflags);
+ }
+
resultRelInfo++;
i++;
}
@@ -1180,12 +1275,19 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
if (operation == CMD_UPDATE || operation == CMD_DELETE)
{
/* For UPDATE/DELETE, find the appropriate junk attr now */
- if (resultRelInfo->ri_RelationDesc->rd_rel->relkind == RELKIND_RELATION)
+ char relkind;
+
+ relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
+ if (relkind == RELKIND_RELATION)
{
j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
if (!AttributeNumberIsValid(j->jf_junkAttNo))
elog(ERROR, "could not find junk ctid column");
}
+ else if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /* FDW must fetch any junk attrs it wants */
+ }
else
{
j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
@@ -1244,6 +1346,19 @@ ExecEndModifyTable(ModifyTableState *node)
int i;
/*
+ * Allow any FDWs to shut down
+ */
+ for (i = 0; i < node->mt_nplans; i++)
+ {
+ ResultRelInfo *resultRelInfo = node->resultRelInfo + i;
+
+ if (resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+ resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
+ resultRelInfo);
+ }
+
+ /*
* Free the exprcontext
*/
ExecFreeExprContext(&node->ps);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 867b0c09d90..fd3823a36ee 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -179,6 +179,7 @@ _copyModifyTable(const ModifyTable *from)
COPY_SCALAR_FIELD(resultRelIndex);
COPY_NODE_FIELD(plans);
COPY_NODE_FIELD(returningLists);
+ COPY_NODE_FIELD(fdwPrivLists);
COPY_NODE_FIELD(rowMarks);
COPY_SCALAR_FIELD(epqParam);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index be4e5482816..d8ce5753a4c 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -333,6 +333,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
WRITE_INT_FIELD(resultRelIndex);
WRITE_NODE_FIELD(plans);
WRITE_NODE_FIELD(returningLists);
+ WRITE_NODE_FIELD(fdwPrivLists);
WRITE_NODE_FIELD(rowMarks);
WRITE_INT_FIELD(epqParam);
}
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 8a51b56dde2..d668128ccb1 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -20,6 +20,7 @@
#include <math.h>
#include "access/skey.h"
+#include "catalog/pg_class.h"
#include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -4695,7 +4696,8 @@ make_result(PlannerInfo *root,
* to make it look better sometime.
*/
ModifyTable *
-make_modifytable(CmdType operation, bool canSetTag,
+make_modifytable(PlannerInfo *root,
+ CmdType operation, bool canSetTag,
List *resultRelations,
List *subplans, List *returningLists,
List *rowMarks, int epqParam)
@@ -4703,7 +4705,10 @@ make_modifytable(CmdType operation, bool canSetTag,
ModifyTable *node = makeNode(ModifyTable);
Plan *plan = &node->plan;
double total_size;
+ List *fdw_private_list;
ListCell *subnode;
+ ListCell *lc;
+ int i;
Assert(list_length(resultRelations) == list_length(subplans));
Assert(returningLists == NIL ||
@@ -4746,6 +4751,53 @@ make_modifytable(CmdType operation, bool canSetTag,
node->rowMarks = rowMarks;
node->epqParam = epqParam;
+ /*
+ * For each result relation that is a foreign table, allow the FDW to
+ * construct private plan data, and accumulate it all into a list.
+ */
+ fdw_private_list = NIL;
+ i = 0;
+ foreach(lc, resultRelations)
+ {
+ Index rti = lfirst_int(lc);
+ FdwRoutine *fdwroutine;
+ List *fdw_private;
+
+ /*
+ * If possible, we want to get the FdwRoutine from our RelOptInfo for
+ * the table. But sometimes we don't have a RelOptInfo and must get
+ * it the hard way. (In INSERT, the target relation is not scanned,
+ * so it's not a baserel; and there are also corner cases for
+ * updatable views where the target rel isn't a baserel.)
+ */
+ if (rti < root->simple_rel_array_size &&
+ root->simple_rel_array[rti] != NULL)
+ {
+ RelOptInfo *resultRel = root->simple_rel_array[rti];
+
+ fdwroutine = resultRel->fdwroutine;
+ }
+ else
+ {
+ RangeTblEntry *rte = planner_rt_fetch(rti, root);
+
+ Assert(rte->rtekind == RTE_RELATION);
+ if (rte->relkind == RELKIND_FOREIGN_TABLE)
+ fdwroutine = GetFdwRoutineByRelId(rte->relid);
+ else
+ fdwroutine = NULL;
+ }
+
+ if (fdwroutine != NULL &&
+ fdwroutine->PlanForeignModify != NULL)
+ fdw_private = fdwroutine->PlanForeignModify(root, node, rti, i);
+ else
+ fdw_private = NIL;
+ fdw_private_list = lappend(fdw_private_list, fdw_private);
+ i++;
+ }
+ node->fdwPrivLists = fdw_private_list;
+
return node;
}
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index db3d5c50182..0847e787c39 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -571,7 +571,8 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
else
rowMarks = root->rowMarks;
- plan = (Plan *) make_modifytable(parse->commandType,
+ plan = (Plan *) make_modifytable(root,
+ parse->commandType,
parse->canSetTag,
list_make1_int(parse->resultRelation),
list_make1(plan),
@@ -964,7 +965,8 @@ inheritance_planner(PlannerInfo *root)
rowMarks = root->rowMarks;
/* And last, tack on a ModifyTable node to do the UPDATE/DELETE work */
- return (Plan *) make_modifytable(parse->commandType,
+ return (Plan *) make_modifytable(root,
+ parse->commandType,
parse->canSetTag,
resultRelations,
subplans,
@@ -2035,6 +2037,15 @@ preprocess_rowmarks(PlannerInfo *root)
if (rte->rtekind != RTE_RELATION)
continue;
+ /*
+ * Similarly, ignore RowMarkClauses for foreign tables; foreign tables
+ * will instead get ROW_MARK_COPY items in the next loop. (FDWs might
+ * choose to do something special while fetching their rows, but that
+ * is of no concern here.)
+ */
+ if (rte->relkind == RELKIND_FOREIGN_TABLE)
+ continue;
+
rels = bms_del_member(rels, rc->rti);
newrc = makeNode(PlanRowMark);
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index 7b28b5584b9..fb67f9e4447 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -6,7 +6,8 @@
* For INSERT and UPDATE queries, the targetlist must contain an entry for
* each attribute of the target relation in the correct order. For all query
* types, we may need to add junk tlist entries for Vars used in the RETURNING
- * list and row ID information needed for EvalPlanQual checking.
+ * list and row ID information needed for SELECT FOR UPDATE locking and/or
+ * EvalPlanQual checking.
*
* NOTE: the rewriter's rewriteTargetListIU and rewriteTargetListUD
* routines also do preprocessing of the targetlist. The division of labor
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index d34fca54666..2a943f9c6a5 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2164,7 +2164,7 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
/*
- * Check for features that are not supported together with FOR [KEY] UPDATE/SHARE.
+ * Check for features that are not supported with FOR [KEY] UPDATE/SHARE.
*
* exported so planner can check again after rewriting, query pullup, etc
*/
@@ -2239,9 +2239,6 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
switch (rte->rtekind)
{
case RTE_RELATION:
- /* ignore foreign tables */
- if (rte->relkind == RELKIND_FOREIGN_TABLE)
- break;
applyLockingClause(qry, i,
lc->strength, lc->noWait, pushedDown);
rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
@@ -2251,7 +2248,7 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
lc->strength, lc->noWait, pushedDown);
/*
- * FOR [KEY] UPDATE/SHARE of subquery is propagated to all of
+ * FOR UPDATE/SHARE of subquery is propagated to all of
* subquery's rels, too. We could do this later (based on
* the marking of the subquery RTE) but it is convenient
* to have local knowledge in each query level about which
@@ -2291,12 +2288,6 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
switch (rte->rtekind)
{
case RTE_RELATION:
- if (rte->relkind == RELKIND_FOREIGN_TABLE)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("row-level locks cannot be used with foreign table \"%s\"",
- rte->eref->aliasname),
- parser_errposition(pstate, thisrel->location)));
applyLockingClause(qry, i,
lc->strength, lc->noWait,
pushedDown);
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 83c83a6a8a4..b99b9930d6a 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -16,6 +16,7 @@
#include "access/sysattr.h"
#include "catalog/pg_type.h"
#include "commands/trigger.h"
+#include "foreign/fdwapi.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "parser/analyze.h"
@@ -1156,6 +1157,7 @@ rewriteValuesRTE(RangeTblEntry *rte, Relation target_relation, List *attrnos)
* is a regular table, the junk TLE emits the ctid attribute of the original
* row. When the target relation is a view, there is no ctid, so we instead
* emit a whole-row Var that will contain the "old" values of the view row.
+ * If it's a foreign table, we let the FDW decide what to add.
*
* For UPDATE queries, this is applied after rewriteTargetListIU. The
* ordering isn't actually critical at the moment.
@@ -1183,6 +1185,21 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
attrname = "ctid";
}
+ else if (target_relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /*
+ * Let the foreign table's FDW add whatever junk TLEs it wants.
+ */
+ FdwRoutine *fdwroutine;
+
+ fdwroutine = GetFdwRoutineForRelation(target_relation, false);
+
+ if (fdwroutine->AddForeignUpdateTargets != NULL)
+ fdwroutine->AddForeignUpdateTargets(parsetree, target_rte,
+ target_relation);
+
+ return;
+ }
else
{
/*
@@ -1444,17 +1461,13 @@ markQueryForLocking(Query *qry, Node *jtnode,
if (rte->rtekind == RTE_RELATION)
{
- /* ignore foreign tables */
- if (rte->relkind != RELKIND_FOREIGN_TABLE)
- {
- applyLockingClause(qry, rti, strength, noWait, pushedDown);
- rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
- }
+ applyLockingClause(qry, rti, strength, noWait, pushedDown);
+ rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
}
else if (rte->rtekind == RTE_SUBQUERY)
{
applyLockingClause(qry, rti, strength, noWait, pushedDown);
- /* FOR [KEY] UPDATE/SHARE of subquery is propagated to subquery's rels */
+ /* FOR UPDATE/SHARE of subquery is propagated to subquery's rels */
markQueryForLocking(rte->subquery, (Node *) rte->subquery->jointree,
strength, noWait, true);
}
diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
index 562d5412df7..485eee320f8 100644
--- a/src/include/foreign/fdwapi.h
+++ b/src/include/foreign/fdwapi.h
@@ -38,9 +38,6 @@ typedef ForeignScan *(*GetForeignPlan_function) (PlannerInfo *root,
List *tlist,
List *scan_clauses);
-typedef void (*ExplainForeignScan_function) (ForeignScanState *node,
- struct ExplainState *es);
-
typedef void (*BeginForeignScan_function) (ForeignScanState *node,
int eflags);
@@ -50,6 +47,48 @@ typedef void (*ReScanForeignScan_function) (ForeignScanState *node);
typedef void (*EndForeignScan_function) (ForeignScanState *node);
+typedef void (*AddForeignUpdateTargets_function) (Query *parsetree,
+ RangeTblEntry *target_rte,
+ Relation target_relation);
+
+typedef List *(*PlanForeignModify_function) (PlannerInfo *root,
+ ModifyTable *plan,
+ Index resultRelation,
+ int subplan_index);
+
+typedef void (*BeginForeignModify_function) (ModifyTableState *mtstate,
+ ResultRelInfo *rinfo,
+ List *fdw_private,
+ int subplan_index,
+ int eflags);
+
+typedef TupleTableSlot *(*ExecForeignInsert_function) (EState *estate,
+ ResultRelInfo *rinfo,
+ TupleTableSlot *slot,
+ TupleTableSlot *planSlot);
+
+typedef TupleTableSlot *(*ExecForeignUpdate_function) (EState *estate,
+ ResultRelInfo *rinfo,
+ TupleTableSlot *slot,
+ TupleTableSlot *planSlot);
+
+typedef TupleTableSlot *(*ExecForeignDelete_function) (EState *estate,
+ ResultRelInfo *rinfo,
+ TupleTableSlot *slot,
+ TupleTableSlot *planSlot);
+
+typedef void (*EndForeignModify_function) (EState *estate,
+ ResultRelInfo *rinfo);
+
+typedef void (*ExplainForeignScan_function) (ForeignScanState *node,
+ struct ExplainState *es);
+
+typedef void (*ExplainForeignModify_function) (ModifyTableState *mtstate,
+ ResultRelInfo *rinfo,
+ List *fdw_private,
+ int subplan_index,
+ struct ExplainState *es);
+
typedef int (*AcquireSampleRowsFunc) (Relation relation, int elevel,
HeapTuple *rows, int targrows,
double *totalrows,
@@ -73,22 +112,34 @@ typedef struct FdwRoutine
{
NodeTag type;
- /*
- * These functions are required.
- */
+ /* Functions for scanning foreign tables */
GetForeignRelSize_function GetForeignRelSize;
GetForeignPaths_function GetForeignPaths;
GetForeignPlan_function GetForeignPlan;
- ExplainForeignScan_function ExplainForeignScan;
BeginForeignScan_function BeginForeignScan;
IterateForeignScan_function IterateForeignScan;
ReScanForeignScan_function ReScanForeignScan;
EndForeignScan_function EndForeignScan;
/*
- * These functions are optional. Set the pointer to NULL for any that are
- * not provided.
+ * Remaining functions are optional. Set the pointer to NULL for any that
+ * are not provided.
*/
+
+ /* Functions for updating foreign tables */
+ AddForeignUpdateTargets_function AddForeignUpdateTargets;
+ PlanForeignModify_function PlanForeignModify;
+ BeginForeignModify_function BeginForeignModify;
+ ExecForeignInsert_function ExecForeignInsert;
+ ExecForeignUpdate_function ExecForeignUpdate;
+ ExecForeignDelete_function ExecForeignDelete;
+ EndForeignModify_function EndForeignModify;
+
+ /* Support functions for EXPLAIN */
+ ExplainForeignScan_function ExplainForeignScan;
+ ExplainForeignModify_function ExplainForeignModify;
+
+ /* Support functions for ANALYZE */
AnalyzeForeignTable_function AnalyzeForeignTable;
} FdwRoutine;
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 76e8cdb1ad8..4f77016652d 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -270,7 +270,8 @@ typedef struct ProjectionInfo
* resultSlot: tuple slot used to hold cleaned tuple.
* junkAttNo: not used by junkfilter code. Can be used by caller
* to remember the attno of a specific junk attribute
- * (execMain.c stores the "ctid" attno here).
+ * (nodeModifyTable.c keeps the "ctid" or "wholerow"
+ * attno here).
* ----------------
*/
typedef struct JunkFilter
@@ -300,6 +301,8 @@ typedef struct JunkFilter
* TrigFunctions cached lookup info for trigger functions
* TrigWhenExprs array of trigger WHEN expr states
* TrigInstrument optional runtime measurements for triggers
+ * FdwRoutine FDW callback functions, if foreign table
+ * FdwState available to save private state of FDW
* ConstraintExprs array of constraint-checking expr states
* junkFilter for removing junk attributes from tuples
* projectReturning for computing a RETURNING list
@@ -317,6 +320,8 @@ typedef struct ResultRelInfo
FmgrInfo *ri_TrigFunctions;
List **ri_TrigWhenExprs;
Instrumentation *ri_TrigInstrument;
+ struct FdwRoutine *ri_FdwRoutine;
+ void *ri_FdwState;
List **ri_ConstraintExprs;
JunkFilter *ri_junkFilter;
ProjectionInfo *ri_projectReturning;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 0b8b1076bbf..841701ed98a 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -173,6 +173,7 @@ typedef struct ModifyTable
int resultRelIndex; /* index of first resultRel in plan's list */
List *plans; /* plan(s) producing source data */
List *returningLists; /* per-target-table RETURNING tlists */
+ List *fdwPrivLists; /* per-target-table FDW private data lists */
List *rowMarks; /* PlanRowMarks (non-locking only) */
int epqParam; /* ID of Param for EvalPlanQual re-eval */
} ModifyTable;
@@ -752,13 +753,32 @@ typedef struct Limit
* RowMarkType -
* enums for types of row-marking operations
*
- * When doing UPDATE, DELETE, or SELECT FOR [KEY] UPDATE/SHARE, we have to uniquely
+ * The first four of these values represent different lock strengths that
+ * we can take on tuples according to SELECT FOR [KEY] UPDATE/SHARE requests.
+ * We only support these on regular tables. For foreign tables, any locking
+ * that might be done for these requests must happen during the initial row
+ * fetch; there is no mechanism for going back to lock a row later (and thus
+ * no need for EvalPlanQual machinery during updates of foreign tables).
+ * This means that the semantics will be a bit different than for a local
+ * table; in particular we are likely to lock more rows than would be locked
+ * locally, since remote rows will be locked even if they then fail
+ * locally-checked restriction or join quals. However, the alternative of
+ * doing a separate remote query to lock each selected row is extremely
+ * unappealing, so let's do it like this for now.
+ *
+ * When doing UPDATE, DELETE, or SELECT FOR UPDATE/SHARE, we have to uniquely
* identify all the source rows, not only those from the target relations, so
* that we can perform EvalPlanQual rechecking at need. For plain tables we
- * can just fetch the TID, the same as for a target relation. Otherwise (for
- * example for VALUES or FUNCTION scans) we have to copy the whole row value.
- * The latter is pretty inefficient but fortunately the case is not
- * performance-critical in practice.
+ * can just fetch the TID, much as for a target relation; this case is
+ * represented by ROW_MARK_REFERENCE. Otherwise (for example for VALUES or
+ * FUNCTION scans) we have to copy the whole row value. ROW_MARK_COPY is
+ * pretty inefficient, since most of the time we'll never need the data; but
+ * fortunately the case is not performance-critical in practice. Note that
+ * we use ROW_MARK_COPY for non-target foreign tables, even if the FDW has a
+ * concept of rowid and so could theoretically support some form of
+ * ROW_MARK_REFERENCE. Although copying the whole row value is inefficient,
+ * it's probably still faster than doing a second remote fetch, so it doesn't
+ * seem worth the extra complexity to permit ROW_MARK_REFERENCE.
*/
typedef enum RowMarkType
{
@@ -776,10 +796,10 @@ typedef enum RowMarkType
* PlanRowMark -
* plan-time representation of FOR [KEY] UPDATE/SHARE clauses
*
- * When doing UPDATE, DELETE, or SELECT FOR [KEY] UPDATE/SHARE, we create a separate
+ * When doing UPDATE, DELETE, or SELECT FOR UPDATE/SHARE, we create a separate
* PlanRowMark node for each non-target relation in the query. Relations that
- * are not specified as FOR [KEY] UPDATE/SHARE are marked ROW_MARK_REFERENCE (if
- * real tables) or ROW_MARK_COPY (if not).
+ * are not specified as FOR UPDATE/SHARE are marked ROW_MARK_REFERENCE (if
+ * regular tables) or ROW_MARK_COPY (if not).
*
* Initially all PlanRowMarks have rti == prti and isParent == false.
* When the planner discovers that a relation is the root of an inheritance
@@ -791,7 +811,7 @@ typedef enum RowMarkType
*
* The planner also adds resjunk output columns to the plan that carry
* information sufficient to identify the locked or fetched rows. For
- * tables (markType != ROW_MARK_COPY), these columns are named
+ * regular tables (markType != ROW_MARK_COPY), these columns are named
* tableoid%u OID of table
* ctid%u TID of row
* The tableoid column is only present for an inheritance hierarchy.
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 2f9fcd575a6..16d685846e8 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);